Переглянути джерело

ghost/notifications/ranking fixes

Viktoriia 9 місяців тому
батько
коміт
7871d14ba9
41 змінених файлів з 1285 додано та 207 видалено
  1. 31 5
      Route.tsx
  2. 8 0
      assets/icons/NMIcon.tsx
  3. 8 0
      assets/icons/TickIcon.tsx
  4. 8 0
      assets/icons/UNIcon.tsx
  5. 10 0
      assets/icons/notifications/bell-solid.svg
  6. 2 0
      assets/icons/notifications/gear-solid.svg
  7. 10 0
      assets/icons/notifications/list-check.svg
  8. 3 0
      assets/icons/notifications/messages.svg
  9. 1 0
      assets/icons/notifications/people-group-solid.svg
  10. 3 0
      assets/icons/unverified.svg
  11. 1 0
      package.json
  12. 2 0
      src/components/ConnectionBanner/ConnectionBanner.tsx
  13. 12 2
      src/components/MenuButton/index.tsx
  14. 14 98
      src/components/MenuDrawer/index.tsx
  15. 147 48
      src/contexts/PushNotificationContext.tsx
  16. 1 1
      src/database/index.ts
  17. 3 0
      src/modules/api/notifications/index.ts
  18. 22 0
      src/modules/api/notifications/notifications-api.ts
  19. 4 0
      src/modules/api/notifications/notifications-query-keys.tsx
  20. 2 0
      src/modules/api/notifications/queries/index.ts
  21. 17 0
      src/modules/api/notifications/queries/use-post-get-settings.tsx
  22. 17 0
      src/modules/api/notifications/queries/use-post-set-settings.tsx
  23. 1 0
      src/modules/api/user/user-api.tsx
  24. 18 11
      src/screens/InAppScreens/MapScreen/UsersListScreen/index.tsx
  25. 8 7
      src/screens/InAppScreens/ProfileScreen/MyFriendsScreen/index.tsx
  26. 19 1
      src/screens/InAppScreens/ProfileScreen/index.tsx
  27. 39 25
      src/screens/InAppScreens/TravellersScreen/Components/Profile.tsx
  28. 46 6
      src/screens/InAppScreens/TravellersScreen/MasterRankingScreen/index.tsx
  29. 1 0
      src/screens/InAppScreens/TravellersScreen/index.tsx
  30. 7 0
      src/screens/InAppScreens/TravellersScreen/utils/sort.ts
  31. 140 0
      src/screens/NotificationsScreen/FriendsNotificactionsScreen/index.tsx
  32. 20 0
      src/screens/NotificationsScreen/FriendsNotificactionsScreen/styles.tsx
  33. 40 0
      src/screens/NotificationsScreen/MessagesNotificationsScreen/index.tsx
  34. 20 0
      src/screens/NotificationsScreen/MessagesNotificationsScreen/styles.tsx
  35. 168 0
      src/screens/NotificationsScreen/NotificationsListScreen/index.tsx
  36. 121 0
      src/screens/NotificationsScreen/NotificationsListScreen/styles.tsx
  37. 40 0
      src/screens/NotificationsScreen/SystemNotificationsScreen/index.tsx
  38. 20 0
      src/screens/NotificationsScreen/SystemNotificationsScreen/styles.tsx
  39. 238 0
      src/screens/NotificationsScreen/index.tsx
  40. 8 3
      src/types/api.ts
  41. 5 0
      src/types/navigation.ts

+ 31 - 5
Route.tsx

@@ -85,6 +85,11 @@ import axios from 'axios';
 import { useNotification } from 'src/contexts/NotificationContext';
 import PreviewScreen from 'src/screens/InAppScreens/ProfileScreen/ShareScreen';
 import { PushNotificationProvider } from 'src/contexts/PushNotificationContext';
+import NotificationsScreen from 'src/screens/NotificationsScreen';
+import NotificationsListScreen from 'src/screens/NotificationsScreen/NotificationsListScreen';
+import FriendsNotificationsScreen from 'src/screens/NotificationsScreen/FriendsNotificactionsScreen';
+import MessagesNotificationsScreen from 'src/screens/NotificationsScreen/MessagesNotificationsScreen';
+import SystemNotificationsScreen from 'src/screens/NotificationsScreen/SystemNotificationsScreen';
 
 enableScreens();
 
@@ -154,10 +159,9 @@ const Route = () => {
   useEffect(() => {
     const prepareApp = async () => {
       await checkNmToken();
-      await findFastestServer();
-      await openDatabases();
       await checkTokenAndUpdate();
       setDbLoaded(true);
+      await findFastestServer();
     };
 
     const findFastestServer = async () => {
@@ -176,6 +180,7 @@ const Route = () => {
     const hideSplashScreen = async () => {
       if (fontsLoaded && dbLoaded) {
         await SplashScreen.hideAsync();
+        await openDatabases();
         await fetchAndSaveUserInfo();
         await setupDatabaseAndSync();
       }
@@ -403,9 +408,30 @@ const Route = () => {
         )}
       </BottomTab.Screen>
       <BottomTab.Screen name={NAVIGATION_PAGES.MENU_DRAWER}>
-        {() => {
-          return null;
-        }}
+        {() => (
+          <ScreenStack.Navigator screenOptions={screenOptions}>
+            <ScreenStack.Screen
+              name={NAVIGATION_PAGES.NOTIFICATIONS}
+              component={NotificationsScreen}
+            />
+            <ScreenStack.Screen
+              name={NAVIGATION_PAGES.NOTIFICATIONS_LIST}
+              component={NotificationsListScreen}
+            />
+            <ScreenStack.Screen
+              name={NAVIGATION_PAGES.FRIENDS_NOTIFICATIONS}
+              component={FriendsNotificationsScreen}
+            />
+            <ScreenStack.Screen
+              name={NAVIGATION_PAGES.MESSAGES_NOTIFICATIONS}
+              component={MessagesNotificationsScreen}
+            />
+            <ScreenStack.Screen
+              name={NAVIGATION_PAGES.SYSTEM_NOTIFICATIONS}
+              component={SystemNotificationsScreen}
+            />
+          </ScreenStack.Navigator>
+        )}
       </BottomTab.Screen>
     </BottomTab.Navigator>
   );

Різницю між файлами не показано, бо вона завелика
+ 8 - 0
assets/icons/NMIcon.tsx


Різницю між файлами не показано, бо вона завелика
+ 8 - 0
assets/icons/TickIcon.tsx


Різницю між файлами не показано, бо вона завелика
+ 8 - 0
assets/icons/UNIcon.tsx


+ 10 - 0
assets/icons/notifications/bell-solid.svg

@@ -0,0 +1,10 @@
+<svg width="14" height="17" viewBox="0 0 14 17" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_3994_36533)">
+<path d="M6.99989 0.5C6.44676 0.5 5.99989 0.946875 5.99989 1.5V2.1C3.71864 2.5625 1.99989 4.58125 1.99989 7V7.5875C1.99989 9.05625 1.45927 10.475 0.484265 11.575L0.253015 11.8344C-0.00948489 12.1281 -0.0719849 12.55 0.0873901 12.9094C0.246765 13.2688 0.60614 13.5 0.99989 13.5H12.9999C13.3936 13.5 13.7499 13.2688 13.9124 12.9094C14.0749 12.55 14.0093 12.1281 13.7468 11.8344L13.5155 11.575C12.5405 10.475 11.9999 9.05937 11.9999 7.5875V7C11.9999 4.58125 10.2811 2.5625 7.99989 2.1V1.5C7.99989 0.946875 7.55301 0.5 6.99989 0.5ZM8.41551 15.9156C8.79051 15.5406 8.99989 15.0312 8.99989 14.5H6.99989H4.99989C4.99989 15.0312 5.20926 15.5406 5.58426 15.9156C5.95926 16.2906 6.46864 16.5 6.99989 16.5C7.53114 16.5 8.04051 16.2906 8.41551 15.9156Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_3994_36533">
+<rect width="14" height="16" fill="white" transform="translate(0 0.5)"/>
+</clipPath>
+</defs>
+</svg>

Різницю між файлами не показано, бо вона завелика
+ 2 - 0
assets/icons/notifications/gear-solid.svg


+ 10 - 0
assets/icons/notifications/list-check.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4001_36561)">
+<path d="M4.7531 1.19441C5.06248 1.47254 5.08748 1.94441 4.80935 2.25379L2.55935 4.75379C2.42185 4.90691 2.2281 4.99754 2.02185 5.00066C1.8156 5.00379 1.61873 4.92566 1.47185 4.78191L0.218726 3.53191C-0.0718994 3.23816 -0.0718994 2.76316 0.218726 2.46941C0.509351 2.17566 0.987476 2.17566 1.2781 2.46941L1.96873 3.16004L3.6906 1.24754C3.96873 0.938162 4.4406 0.913162 4.74998 1.19129L4.7531 1.19441ZM4.7531 6.19441C5.06248 6.47254 5.08748 6.94441 4.80935 7.25379L2.55935 9.75379C2.42185 9.90691 2.2281 9.99754 2.02185 10.0007C1.8156 10.0038 1.61873 9.92566 1.47185 9.78191L0.218726 8.53191C-0.0750244 8.23816 -0.0750244 7.76316 0.218726 7.47254C0.512476 7.18191 0.987476 7.17879 1.2781 7.47254L1.96873 8.16316L3.6906 6.25066C3.96873 5.94129 4.4406 5.91629 4.74998 6.19441H4.7531ZM6.99998 3.00066C6.99998 2.44754 7.44685 2.00066 7.99998 2.00066H15C15.5531 2.00066 16 2.44754 16 3.00066C16 3.55379 15.5531 4.00066 15 4.00066H7.99998C7.44685 4.00066 6.99998 3.55379 6.99998 3.00066ZM6.99998 8.00066C6.99998 7.44754 7.44685 7.00066 7.99998 7.00066H15C15.5531 7.00066 16 7.44754 16 8.00066C16 8.55379 15.5531 9.00066 15 9.00066H7.99998C7.44685 9.00066 6.99998 8.55379 6.99998 8.00066ZM4.99998 13.0007C4.99998 12.4475 5.44685 12.0007 5.99998 12.0007H15C15.5531 12.0007 16 12.4475 16 13.0007C16 13.5538 15.5531 14.0007 15 14.0007H5.99998C5.44685 14.0007 4.99998 13.5538 4.99998 13.0007ZM1.49998 11.5007C1.8978 11.5007 2.27933 11.6587 2.56064 11.94C2.84194 12.2213 2.99998 12.6028 2.99998 13.0007C2.99998 13.3985 2.84194 13.78 2.56064 14.0613C2.27933 14.3426 1.8978 14.5007 1.49998 14.5007C1.10215 14.5007 0.72062 14.3426 0.439315 14.0613C0.158011 13.78 -2.4423e-05 13.3985 -2.4423e-05 13.0007C-2.4423e-05 12.6028 0.158011 12.2213 0.439315 11.94C0.72062 11.6587 1.10215 11.5007 1.49998 11.5007Z"/>
+</g>
+<defs>
+<clipPath id="clip0_4001_36561">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
assets/icons/notifications/messages.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="13" viewBox="0 0 16 13" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5.20045 8.8003C8.07305 8.8003 10.4006 6.83023 10.4006 4.40015C10.4006 1.97007 8.07305 0 5.20045 0C2.32785 0 0.00027362 1.97007 0.00027362 4.40015C0.00027362 5.36518 0.367786 6.25771 0.990307 6.98524C0.902804 7.22024 0.7728 7.42775 0.635295 7.60276C0.515291 7.75776 0.392787 7.87777 0.302784 7.96027C0.257782 8.00027 0.220281 8.03277 0.19528 8.05277C0.18278 8.06277 0.172779 8.07027 0.167779 8.07277L0.162779 8.07777C0.0252745 8.18028 -0.0347276 8.36028 0.0202743 8.52279C0.0752762 8.68529 0.227781 8.8003 0.400287 8.8003C0.945306 8.8003 1.49532 8.66029 1.95284 8.48779C2.18285 8.40028 2.39785 8.30278 2.58536 8.20278C3.35289 8.58279 4.24542 8.8003 5.20045 8.8003ZM11.2007 4.40015C11.2007 7.20774 8.72307 9.32282 5.78797 9.57532C6.39549 11.4354 8.41056 12.8004 10.8006 12.8004C11.7557 12.8004 12.6482 12.5829 13.4182 12.2029C13.6057 12.3029 13.8182 12.4004 14.0482 12.4879C14.5058 12.6604 15.0558 12.8004 15.6008 12.8004C15.7733 12.8004 15.9283 12.6879 15.9808 12.5229C16.0333 12.3579 15.9758 12.1779 15.8358 12.0754L15.8308 12.0704C15.8258 12.0654 15.8158 12.0604 15.8033 12.0504C15.7783 12.0304 15.7408 12.0004 15.6958 11.9579C15.6058 11.8754 15.4833 11.7554 15.3633 11.6004C15.2258 11.4254 15.0958 11.2154 15.0083 10.9829C15.6308 10.2578 15.9983 9.36532 15.9983 8.39778C15.9983 6.07771 13.8757 4.17514 11.1832 4.01014C11.1932 4.13764 11.1982 4.26764 11.1982 4.39765L11.2007 4.40015Z"/>
+</svg>

Різницю між файлами не показано, бо вона завелика
+ 1 - 0
assets/icons/notifications/people-group-solid.svg


+ 3 - 0
assets/icons/unverified.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99518 0C10.1748 0 10.3544 0.0390433 10.5184 0.113226L17.8702 3.23279C18.7292 3.59589 19.3695 4.44313 19.3656 5.46607C19.3461 9.33917 17.7531 16.4255 11.0259 19.6466C10.3739 19.959 9.61646 19.959 8.96443 19.6466C2.23726 16.4255 0.644295 9.33917 0.624774 5.46607C0.620869 4.44313 1.26118 3.59589 2.12013 3.23279L9.4759 0.113226C9.63598 0.0390433 9.81558 0 9.99518 0ZM6.83266 6.83258C6.46566 7.19959 6.46566 7.79305 6.83266 8.15615L8.6677 9.99119L6.83266 11.8262C6.46566 12.1932 6.46566 12.7867 6.83266 13.1498C7.19967 13.5129 7.79313 13.5168 8.15623 13.1498L9.99127 11.3148L11.8263 13.1498C12.1933 13.5168 12.7868 13.5168 13.1499 13.1498C13.513 12.7828 13.5169 12.1893 13.1499 11.8262L11.3148 9.99119L13.1499 8.15615C13.5169 7.78915 13.5169 7.19569 13.1499 6.83258C12.7829 6.46948 12.1894 6.46558 11.8263 6.83258L9.99127 8.66762L8.15623 6.83258C7.78923 6.46558 7.19577 6.46558 6.83266 6.83258Z" fill="#EF5B5B"/>
+</svg>

+ 1 - 0
package.json

@@ -50,6 +50,7 @@
     "react-native": "0.74.5",
     "react-native-animated-pagination-dot": "^0.4.0",
     "react-native-calendars": "^1.1304.1",
+    "react-native-color-matrix-image-filters": "^7.0.1",
     "react-native-device-detection": "^0.2.1",
     "react-native-gesture-handler": "~2.16.1",
     "react-native-google-places-autocomplete": "^2.5.6",

+ 2 - 0
src/components/ConnectionBanner/ConnectionBanner.tsx

@@ -3,6 +3,7 @@ import { View, Text } from 'react-native';
 import { useConnection } from 'src/contexts/ConnectionContext';
 import { styles } from './styles';
 import { initializeBanner, showBanner } from 'src/utils/bannerUtils';
+import { refreshDatabases } from 'src/db';
 
 const ConnectionBanner = () => {
   const netInfo = useConnection();
@@ -15,6 +16,7 @@ const ConnectionBanner = () => {
 
     if (netInfo?.isInternetReachable !== null) {
       if (hadConnection === false && netInfo?.isInternetReachable) {
+        refreshDatabases();
         showBanner('Internet connection restored!');
       } else if (netInfo?.isInternetReachable === false) {
         showBanner('No internet connection!');

+ 12 - 2
src/components/MenuButton/index.tsx

@@ -12,19 +12,29 @@ type ButtonProps = {
   icon: ReactNode;
   buttonFn?: (navigate: NavigationProp<ReactNavigation.RootParamList>) => void;
   red?: boolean;
+  disabled?: boolean;
 };
 
-export const MenuButton: FC<ButtonProps> = ({ label, icon, buttonFn, red }) => {
+export const MenuButton: FC<ButtonProps> = ({ label, icon, buttonFn, red, disabled }) => {
   const navigation = useNavigation();
 
   return (
     <TouchableOpacity
       style={[styles.alignStyle, styles.buttonWrapper, { justifyContent: 'space-between' }]}
       onPress={() => (buttonFn ? buttonFn(navigation) : null)}
+      disabled={disabled}
     >
       <View style={styles.alignStyle}>
         {icon}
-        <Text style={[styles.buttonLabel, red ? { color: Colors.RED } : null]}>{label}</Text>
+        <Text
+          style={[
+            styles.buttonLabel,
+            red ? { color: Colors.RED } : null,
+            disabled ? { color: Colors.LIGHT_GRAY } : null
+          ]}
+        >
+          {label}
+        </Text>
       </View>
       <View>
         <ArrowRightIcon fill={red ? Colors.RED : Colors.LIGHT_GRAY} width={20} height={20} />

+ 14 - 98
src/components/MenuDrawer/index.tsx

@@ -1,7 +1,6 @@
 import React, { useState } from 'react';
-import { View, Image, Linking, Text, Switch, Platform } from 'react-native';
+import { View, Image, Linking, Text } from 'react-native';
 import { CommonActions, useNavigation } from '@react-navigation/native';
-import * as Notifications from 'expo-notifications';
 
 import { WarningModal } from '../WarningModal';
 import { MenuButton } from '../MenuButton';
@@ -16,11 +15,10 @@ import DocumentIcon from '../../../assets/icons/document.svg';
 import ExitIcon from '../../../assets/icons/exit.svg';
 import UserXMark from '../../../assets/icons/user-xmark.svg';
 import InfoIcon from 'assets/icons/info-solid.svg';
+import BellIcon from 'assets/icons/notifications/bell-solid.svg';
 
 import { APP_VERSION, FASTEST_MAP_HOST } from 'src/constants';
 import { useNotification } from 'src/contexts/NotificationContext';
-import { usePostSaveNotificationTokenMutation } from '@api/user';
-import { usePushNotification } from 'src/contexts/PushNotificationContext';
 
 export const MenuDrawer = (props: any) => {
   const { mutate: deleteUser } = useDeleteUserMutation();
@@ -33,9 +31,6 @@ export const MenuDrawer = (props: any) => {
     action: () => {}
   });
   const { updateNotificationStatus } = useNotification();
-  const { mutateAsync: saveNotificationToken } = usePostSaveNotificationTokenMutation();
-  const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState(false);
-  const { isSubscribed, toggleSubscription } = usePushNotification();
 
   const openModal = (type: string, message: string, action: any) => {
     setModalInfo({
@@ -69,72 +64,6 @@ export const MenuDrawer = (props: any) => {
     deleteUser({ token }, { onSuccess: handleLogout });
   };
 
-  const handleSubscribe = async () => {
-    const deviceData = await registerForPushNotificationsAsync();
-
-    if (deviceData?.notificationToken) {
-      toggleSubscription();
-      await saveNotificationToken({
-        token,
-        platform: deviceData.platform,
-        n_token: deviceData.notificationToken
-      });
-    }
-  };
-
-  const toggleSwitch = async () => {
-    if (isSubscribed) {
-      toggleSubscription();
-    } else {
-      const { status } = await Notifications.getPermissionsAsync();
-      if (status !== 'granted') {
-        setModalInfo({
-          visible: true,
-          type: 'success',
-          message:
-            'To use this feature we need your permission to access your notifications. If you press OK your system will ask you to confirm permission to receive notifications from NomadMania.',
-          action: () => setShouldOpenWarningModal(true)
-        });
-      } else {
-        handleSubscribe();
-      }
-    }
-  };
-
-  async function registerForPushNotificationsAsync() {
-    const { status: existingStatus } = await Notifications.getPermissionsAsync();
-    let finalStatus = existingStatus;
-    if (existingStatus !== 'granted') {
-      const { status } = await Notifications.requestPermissionsAsync();
-      finalStatus = status;
-    }
-    if (finalStatus !== 'granted') {
-      setModalInfo({
-        visible: true,
-        type: 'success',
-        message:
-          'NomadMania app needs notification permissions to function properly. Open settings?',
-        action: () =>
-          Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
-      });
-      return null;
-    }
-    const deviceData = await Notifications.getDevicePushTokenAsync();
-    console.log('deviceData', deviceData);
-
-    if (Platform.OS === 'android') {
-      Notifications.setNotificationChannelAsync('default', {
-        name: 'default',
-        importance: Notifications.AndroidImportance.MAX,
-        vibrationPattern: [0, 250, 250, 250],
-        lightColor: '#FF231F7C'
-      });
-    }
-    storage.set('deviceToken', deviceData.data);
-
-    return { notificationToken: deviceData.data ?? '', platform: deviceData.type ?? '' };
-  }
-
   return (
     <>
       <View style={styles.container}>
@@ -160,26 +89,19 @@ export const MenuDrawer = (props: any) => {
             red={false}
             buttonFn={() => Linking.openURL('https://nomadmania.com/terms/')}
           />
-          <View
-            style={{
-              display: 'flex',
-              flexDirection: 'row',
-              justifyContent: 'space-between',
-              marginTop: 20,
-              alignItems: 'center'
-            }}
-          >
-            <Text style={{ color: Colors.DARK_BLUE, fontSize: 16, fontWeight: 'bold' }}>
-              Notifications
-            </Text>
-            <Switch
-              trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
-              thumbColor={Colors.WHITE}
-              onValueChange={toggleSwitch}
-              value={isSubscribed}
-              style={{ transform: 'scale(0.8)' }}
+          {token && (
+            <MenuButton
+              label="Notifications"
+              icon={<BellIcon fill={Colors.DARK_BLUE} width={20} height={20} />}
+              red={false}
+              buttonFn={() =>
+                // todo: add types
+                navigation.navigate(NAVIGATION_PAGES.MENU_DRAWER, {
+                  screen: NAVIGATION_PAGES.NOTIFICATIONS
+                })
+              }
             />
-          </View>
+          )}
         </View>
 
         <View style={styles.bottomMenu}>
@@ -227,12 +149,6 @@ export const MenuDrawer = (props: any) => {
           modalInfo.action();
           closeModal();
         }}
-        onModalHide={() => {
-          if (shouldOpenWarningModal) {
-            setShouldOpenWarningModal(false);
-            handleSubscribe();
-          }
-        }}
       />
     </>
   );

+ 147 - 48
src/contexts/PushNotificationContext.tsx

@@ -2,16 +2,26 @@ import React, { useEffect, useState, useContext, createContext } from 'react';
 import * as Notifications from 'expo-notifications';
 import { storage, StoreType } from 'src/storage';
 import { Linking, Platform } from 'react-native';
-import { useNavigation } from '@react-navigation/native';
-
-const PushNotificationContext = createContext(null);
+import { CommonActions, useNavigation } from '@react-navigation/native';
+import { NAVIGATION_PAGES } from 'src/types';
+import { usePostSetSettingsMutation } from '@api/notifications';
+
+const PushNotificationContext = createContext<{
+  isSubscribed: boolean;
+  toggleSubscription: () => Promise<void>;
+}>({
+  isSubscribed: false,
+  toggleSubscription: async () => {}
+});
 
 export const usePushNotification = () => useContext(PushNotificationContext);
 
-export const PushNotificationProvider = ({ children }) => {
+export const PushNotificationProvider = ({ children }: { children: React.ReactNode }) => {
+  const token = storage.get('token', StoreType.STRING) as string;
   const [isSubscribed, setIsSubscribed] = useState(
     (storage.get('subscribed', StoreType.BOOLEAN) as boolean) ?? false
   );
+  const { mutateAsync: setNotificationsSettings } = usePostSetSettingsMutation();
   const navigation = useNavigation();
 
   const lastNotificationResponse = Notifications.useLastNotificationResponse();
@@ -28,23 +38,57 @@ export const PushNotificationProvider = ({ children }) => {
         if (data?.params) {
           const parsedParams = JSON.parse(data.params) ?? {};
 
-          navigation.navigate(
-            ...([
-              data.parentScreen,
-              {
-                screen: data.screen,
-                params: parsedParams
-              }
-            ] as never)
+          navigation.dispatch(
+            CommonActions.reset({
+              index: 1,
+              routes: [
+                {
+                  name: 'DrawerApp',
+                  state: {
+                    routes: [
+                      {
+                        name: data.parentScreen,
+                        state: {
+                          routes: [
+                            { name: NAVIGATION_PAGES.MAP_TAB },
+                            {
+                              name: data.screen,
+                              params: parsedParams
+                            }
+                          ]
+                        }
+                      }
+                    ]
+                  }
+                }
+              ]
+            })
           );
         } else {
-          navigation.navigate(
-            ...([
-              data.parentScreen,
-              {
-                screen: data.screen
-              }
-            ] as never)
+          navigation.dispatch(
+            CommonActions.reset({
+              index: 1,
+              routes: [
+                {
+                  name: 'DrawerApp',
+                  state: {
+                    routes: [
+                      {
+                        name: data.parentScreen,
+                        state: {
+                          routes: [
+                            { name: NAVIGATION_PAGES.MAP_TAB },
+                            {
+                              name: data.screen
+                            }
+                          ]
+                        }
+                      }
+                    ]
+                  }
+                }
+              ]
+            })
           );
         }
       }
@@ -56,52 +100,98 @@ export const PushNotificationProvider = ({ children }) => {
 
   useEffect(() => {
     if (isSubscribed) {
+      Notifications.setNotificationHandler({
+        handleNotification: async () => ({
+          shouldShowAlert: true,
+          shouldPlaySound: false,
+          shouldSetBadge: false
+        })
+      });
+
       const notificationListener = Notifications.addNotificationReceivedListener((notification) => {
-        console.log('Notification received', notification.request);
+        console.log('Notification received');
       });
 
       const responseListener = Notifications.addNotificationResponseReceivedListener((response) => {
-        console.log('Notification response received', response.notification.request);
+        console.log('Notification response received');
 
         let screenName;
         let url;
         let parentScreen;
         let params;
         if (Platform.OS === 'ios') {
-          console.log('data ios', response.notification.request.trigger?.payload);
-          parentScreen = response.notification.request.trigger?.payload?.parentScreen;
-          screenName = response.notification.request.trigger?.payload?.screen;
-          params = response.notification.request.trigger?.payload?.params;
-
-          url = response.notification.request.trigger?.payload?.url;
+          parentScreen = (
+            response.notification.request.trigger as Notifications.PushNotificationTrigger
+          )?.payload?.parentScreen;
+          screenName = (
+            response.notification.request.trigger as Notifications.PushNotificationTrigger
+          )?.payload?.screen;
+          params = (response.notification.request.trigger as Notifications.PushNotificationTrigger)
+            ?.payload?.params;
+          url = (response.notification.request.trigger as Notifications.PushNotificationTrigger)
+            ?.payload?.url;
         }
 
         if (screenName && parentScreen) {
           if (params) {
-            const parsedParams = JSON.parse(params) ?? {};
-
-            navigation.navigate(
-              ...([
-                parentScreen,
-                {
-                  screen: screenName,
-                  params: parsedParams
-                }
-              ] as never)
+            const parsedParams = JSON.parse(params as string) ?? {};
+
+            navigation.dispatch(
+              CommonActions.reset({
+                index: 1,
+                routes: [
+                  {
+                    name: 'DrawerApp',
+                    state: {
+                      routes: [
+                        {
+                          name: parentScreen as string,
+                          state: {
+                            routes: [
+                              { name: NAVIGATION_PAGES.MAP_TAB },
+                              {
+                                name: screenName as string,
+                                params: parsedParams
+                              }
+                            ]
+                          }
+                        }
+                      ]
+                    }
+                  }
+                ]
+              })
             );
           } else {
-            navigation.navigate(
-              ...([
-                parentScreen,
-                {
-                  screen: screenName
-                }
-              ] as never)
+            navigation.dispatch(
+              CommonActions.reset({
+                index: 1,
+                routes: [
+                  {
+                    name: 'DrawerApp',
+                    state: {
+                      routes: [
+                        {
+                          name: parentScreen as string,
+                          state: {
+                            routes: [
+                              { name: NAVIGATION_PAGES.MAP_TAB },
+                              {
+                                name: screenName as string
+                              }
+                            ]
+                          }
+                        }
+                      ]
+                    }
+                  }
+                ]
+              })
             );
           }
         }
         if (url) {
-          Linking.openURL(url);
+          Linking.openURL(url as string);
         }
       });
 
@@ -118,7 +208,18 @@ export const PushNotificationProvider = ({ children }) => {
   };
 
   const unsubscribeFromNotifications = async () => {
-    await removeNotificationTokenFromServer();
+    const settings = {
+      'app-ios': 0,
+      'app-android': 0,
+      'app-friends': 0,
+      'email-friends': 0
+    };
+
+    await setNotificationsSettings({
+      token,
+      settings: JSON.stringify(settings)
+    });
+
     storage.remove('deviceToken');
     storage.set('subscribed', false);
     setIsSubscribed(false);
@@ -138,5 +239,3 @@ export const PushNotificationProvider = ({ children }) => {
     </PushNotificationContext.Provider>
   );
 };
-
-async function removeNotificationTokenFromServer() {}

+ 1 - 1
src/database/index.ts

@@ -67,7 +67,7 @@ export const setupDatabaseAndSync = async (): Promise<void> => {
   await updateStaticGeoJSON();
 };
 
-const updateMasterRanking = async () => {
+export const updateMasterRanking = async () => {
   const token = storage.get('token', StoreType.STRING) as string || '';
   const dataLimitedRanking = await fetchLimitedRanking();
 

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

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

+ 22 - 0
src/modules/api/notifications/notifications-api.ts

@@ -0,0 +1,22 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetSettings {
+  settings: {
+    name: 'app-ios' | 'app-android' | 'app-friends' | 'email-friends';
+    active: 0 | 1;
+  }[];
+}
+
+export interface Settings {
+  token: string;
+  settings: string;
+}
+
+export const notificationsApi = {
+  getSettings: (token: string) =>
+    request.postForm<PostGetSettings>(API.GET_NOTIFICATIONS_SETTINGS, { token }),
+  setSettings: (data: Settings) =>
+    request.postForm<ResponseType>(API.SET_NOTIFICATIONS_SETTINGS, data)
+};

+ 4 - 0
src/modules/api/notifications/notifications-query-keys.tsx

@@ -0,0 +1,4 @@
+export const notificationsQueryKeys = {
+  getSettings: (token: string) => ['getSettings', token] as const,
+  setSettings: () => ['setSettings'] as const
+};

+ 2 - 0
src/modules/api/notifications/queries/index.ts

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

+ 17 - 0
src/modules/api/notifications/queries/use-post-get-settings.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { notificationsQueryKeys } from '../notifications-query-keys';
+import { notificationsApi, type PostGetSettings } from '../notifications-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetSettingsQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetSettings, BaseAxiosError>({
+    queryKey: notificationsQueryKeys.getSettings(token),
+    queryFn: async () => {
+      const response = await notificationsApi.getSettings(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/notifications/queries/use-post-set-settings.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { notificationsQueryKeys } from '../notifications-query-keys';
+import { type Settings, notificationsApi } from '../notifications-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostSetSettingsMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, Settings, ResponseType>({
+    mutationKey: notificationsQueryKeys.setSettings(),
+    mutationFn: async (data) => {
+      const response = await notificationsApi.setSettings(data);
+      return response.data;
+    }
+  });
+};

+ 1 - 0
src/modules/api/user/user-api.tsx

@@ -260,6 +260,7 @@ export interface PostGetProfileDataReturn extends ResponseType {
       supreme: 0 | 1;
       ukr: 0 | 1;
       homeregion: string;
+      badge_ghost: 0 | 1;
     };
   };
 }

+ 18 - 11
src/screens/InAppScreens/MapScreen/UsersListScreen/index.tsx

@@ -17,7 +17,10 @@ import { RankingDropdown } from '../../TravellersScreen/utils/types';
 import { dataRanking } from '../../TravellersScreen/utils';
 import { Ranking } from '../../TravellersScreen';
 import { useGetFriendsMutation } from '@api/friends';
-import { useGetUsersFromCountryMutation, useGetUsersWhoVisitedCountryMutation } from '@api/countries';
+import {
+  useGetUsersFromCountryMutation,
+  useGetUsersWhoVisitedCountryMutation
+} from '@api/countries';
 
 type Props = {
   navigation: NavigationProp<any>;
@@ -235,7 +238,8 @@ const UsersListScreen: FC<Props> = ({ navigation, route }) => {
             setUsers(data?.data?.users);
             setSelectedUsers(data?.data?.users);
             setMaxPages(data?.data?.max_pages);
-            !masterCountries?.length && setMasterCountries(convertData(data?.data?.countries) ?? []);
+            !masterCountries?.length &&
+              setMasterCountries(convertData(data?.data?.countries) ?? []);
             setLoading(false);
           }
         }
@@ -254,7 +258,8 @@ const UsersListScreen: FC<Props> = ({ navigation, route }) => {
             setUsers(data?.data?.users);
             setSelectedUsers(data?.data?.users);
             setMaxPages(data?.data?.max_pages);
-            !masterCountries?.length && setMasterCountries(convertData(data?.data?.countries) ?? []);
+            !masterCountries?.length &&
+              setMasterCountries(convertData(data?.data?.countries) ?? []);
             setLoading(false);
           }
         }
@@ -273,7 +278,8 @@ const UsersListScreen: FC<Props> = ({ navigation, route }) => {
             setUsers(data?.data?.users);
             setSelectedUsers(data?.data?.users);
             setMaxPages(data?.data?.max_pages);
-            !masterCountries?.length && setMasterCountries(convertData(data?.data?.countries) ?? []);
+            !masterCountries?.length &&
+              setMasterCountries(convertData(data?.data?.countries) ?? []);
             setLoading(false);
           }
         }
@@ -283,13 +289,14 @@ const UsersListScreen: FC<Props> = ({ navigation, route }) => {
 
   const convertData = (data: { [key: string]: { country: string; flag: string } }) => {
     let formatedCountries = [{ two: 'all', name: 'ALL', flag: '' }];
-    formatedCountries.push(
-      ...Object.entries(data).map(([key, value]) => ({
-        two: key,
-        name: value.country,
-        flag: value.flag
-      }))
-    );
+    data &&
+      formatedCountries.push(
+        ...Object.entries(data).map(([key, value]) => ({
+          two: key,
+          name: value.country,
+          flag: value.flag
+        }))
+      );
 
     return formatedCountries;
   };

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

@@ -164,13 +164,14 @@ const MyFriendsScreen: FC<Props> = ({ navigation }) => {
 
   const convertData = (data: { [key: string]: { country: string; flag: string } }) => {
     let formatedCountries = [{ two: 'all', name: 'ALL', flag: '' }];
-    formatedCountries.push(
-      ...Object.entries(data).map(([key, value]) => ({
-        two: key,
-        name: value.country,
-        flag: value.flag
-      }))
-    );
+    data &&
+      formatedCountries.push(
+        ...Object.entries(data).map(([key, value]) => ({
+          two: key,
+          name: value.country,
+          flag: value.flag
+        }))
+      );
 
     return formatedCountries;
   };

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

@@ -32,11 +32,13 @@ import UNIcon from '../../../../assets/icons/un_icon.svg';
 import NMIcon from '../../../../assets/icons/nm_icon.svg';
 import ChevronIcon from '../../../../assets/icons/chevron-left.svg';
 import ShareIcon from '../../../../assets/icons/share.svg';
+import UnverifiedIcon from '../../../../assets/icons/unverified.svg';
 
 import { ProfileStyles, ScoreStyles, TBTStyles } from '../TravellersScreen/Components/styles';
 import UnauthenticatedProfileScreen from './UnauthenticatedProfileScreen';
 import { PersonalInfo } from './Components/PersonalInfo';
 import { usePostUpdateFriendStatusMutation } from '@api/friends';
+import Tooltip from 'react-native-walkthrough-tooltip';
 
 type Props = {
   navigation: NavigationProp<any>;
@@ -68,6 +70,7 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
     isModalVisible: false,
     isWarningVisible: false
   });
+  const [tooltipVisible, setTooltipVisible] = useState(false);
 
   useFocusEffect(
     useCallback(() => {
@@ -255,6 +258,21 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
                   {data.user_data.auth ? <TickIcon /> : null}
                   {data.user_data.badge_un ? <UNIcon /> : null}
                   {data.user_data.badge_nm ? <NMIcon /> : null}
+                  {data.user_data.badge_ghost ? (
+                    <Tooltip
+                      isVisible={tooltipVisible}
+                      onClose={() => setTooltipVisible(false)}
+                      content={<Text style={{ color: Colors.DARK_BLUE }}>Unverified User</Text>}
+                      contentStyle={{ backgroundColor: Colors.WHITE }}
+                      backgroundColor="transparent"
+                      allowChildInteraction={false}
+                      placement="top"
+                    >
+                      <TouchableOpacity onPress={() => setTooltipVisible(true)}>
+                        <UnverifiedIcon />
+                      </TouchableOpacity>
+                    </Tooltip>
+                  ) : null}
                 </View>
               </View>
 
@@ -286,7 +304,7 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
                       style={{ alignSelf: 'center' }}
                     />
                   </TouchableOpacity>
-                  
+
                   <TouchableOpacity
                     style={styles.settings}
                     onPress={() => navigation.navigate(NAVIGATION_PAGES.EDIT_PERSONAL_INFO)}

+ 39 - 25
src/screens/InAppScreens/TravellersScreen/Components/Profile.tsx

@@ -3,6 +3,8 @@ import { Text, TouchableOpacity, View } from 'react-native';
 import { Image } from 'expo-image';
 import { useNavigation } from '@react-navigation/native';
 import * as FileSystem from 'expo-file-system';
+import Tooltip from 'react-native-walkthrough-tooltip';
+import { Grayscale } from 'react-native-color-matrix-image-filters';
 
 import { ProfileStyles, ScoreStyles, TBTStyles } from './styles';
 
@@ -13,14 +15,14 @@ import { API_HOST } from '../../../../constants';
 import { getFontSize } from '../../../../utils';
 import { adaptiveStyle, Colors } from '../../../../theme';
 
-import TickIcon from '../../../../../assets/icons/tick.svg';
-import UNIcon from '../../../../../assets/icons/un_icon.svg';
-import NMIcon from '../../../../../assets/icons/nm_icon.svg';
+import TickIcon from '../../../../../assets/icons/TickIcon';
+import UNIcon from '../../../../../assets/icons/UNIcon';
+import NMIcon from '../../../../../assets/icons/NMIcon';
 import TBTIcon from '../../../../../assets/icons/tbt.svg';
+import MonumentIcon from '../../../../../assets/icons/monument.svg';
 
 import { NAVIGATION_PAGES } from '../../../../types';
 import { useConnection } from 'src/contexts/ConnectionContext';
-import Tooltip from 'react-native-walkthrough-tooltip';
 
 type Props = {
   avatar: string | null;
@@ -215,23 +217,31 @@ export const Profile: FC<Props> = ({
           }
         ]}
       >
-        <Text style={adaptiveStyle(ScoreStyles.rankText, {})}>{index + 1}</Text>
+        {index === -1 ? (
+          <MonumentIcon fill={Colors.DARK_BLUE} />
+        ) : (
+          <Text style={adaptiveStyle(ScoreStyles.rankText, {})}>{index + 1}</Text>
+        )}
       </View>
       <View style={{ flex: 1, paddingLeft: 8, paddingRight: 3, paddingBottom: 3 }}>
         <TouchableOpacity onPress={() => handlePress()}>
           <View style={adaptiveStyle(ProfileStyles.profileRoot, {})}>
             <View style={ProfileStyles.avatarContainer}>
               {avatar ? (
-                <Image
-                  style={adaptiveStyle(ProfileStyles.profileAvatar, {})}
-                  source={{ uri: avatarBaseUri + avatar }}
-                />
+                <Grayscale amount={index === -1 ? 1 : 0}>
+                  <Image
+                    style={adaptiveStyle(ProfileStyles.profileAvatar, {})}
+                    source={{ uri: avatarBaseUri + avatar }}
+                  />
+                </Grayscale>
               ) : homebase_flag ? (
-                <AvatarWithInitials
-                  text={`${first_name[0] ?? ''}${last_name[0] ?? ''}`}
-                  flag={flagBaseUri + homebase_flag}
-                  size={48}
-                />
+                <Grayscale amount={index === -1 ? 1 : 0}>
+                  <AvatarWithInitials
+                    text={`${first_name[0] ?? ''}${last_name[0] ?? ''}`}
+                    flag={flagBaseUri + homebase_flag}
+                    size={48}
+                  />
+                </Grayscale>
               ) : null}
             </View>
             <View style={adaptiveStyle(ProfileStyles.profileDataRoot, {})}>
@@ -249,21 +259,25 @@ export const Profile: FC<Props> = ({
                     Age: {date_of_birth ?? ''}
                   </Text>
                   {homebase_flag && (
-                    <Image
-                      source={{ uri: flagBaseUri + homebase_flag }}
-                      style={adaptiveStyle(ProfileStyles.countryFlag, {})}
-                    />
+                    <Grayscale amount={index === -1 ? 1 : 0}>
+                      <Image
+                        source={{ uri: flagBaseUri + homebase_flag }}
+                        style={adaptiveStyle(ProfileStyles.countryFlag, {})}
+                      />
+                    </Grayscale>
                   )}
                   {homebase2_flag && homebase2_flag !== homebase_flag ? (
-                    <Image
-                      source={{ uri: flagBaseUri + homebase2_flag }}
-                      style={adaptiveStyle([ProfileStyles.countryFlag, { marginLeft: -15 }], {})}
-                    />
+                    <Grayscale amount={index === -1 ? 1 : 0}>
+                      <Image
+                        source={{ uri: flagBaseUri + homebase2_flag }}
+                        style={adaptiveStyle([ProfileStyles.countryFlag, { marginLeft: -15 }], {})}
+                      />
+                    </Grayscale>
                   ) : null}
                   <View style={adaptiveStyle(ProfileStyles.badgesWrapper, {})}>
-                    {auth ? <TickIcon /> : null}
-                    {badge_un ? <UNIcon /> : null}
-                    {badge_1281 ? <NMIcon /> : null}
+                    {auth ? <TickIcon isBlackAndWhite={index === -1} /> : null}
+                    {badge_un ? <UNIcon isBlackAndWhite={index === -1} /> : null}
+                    {badge_1281 ? <NMIcon isBlackAndWhite={index === -1} /> : null}
                   </View>
                 </View>
               </View>

+ 46 - 6
src/screens/InAppScreens/TravellersScreen/MasterRankingScreen/index.tsx

@@ -15,6 +15,7 @@ import type { RankingDropdown } from '../utils/types';
 
 import type { Ranking } from '..';
 import { useConnection } from 'src/contexts/ConnectionContext';
+import { updateMasterRanking } from 'src/database';
 
 const MasterRankingScreen = () => {
   const { data: fullData } = useGetFullRanking();
@@ -33,11 +34,38 @@ const MasterRankingScreen = () => {
     useCallback(() => {
       const fetchRanking = async () => {
         const ranking = storage.get('masterRanking', StoreType.STRING) as string;
+        let indexCounter = 0;
+        if (!ranking) {
+          updateMasterRanking();
+          setMasterRanking(
+            fullData
+              ? fullData?.data
+                  ?.sort(
+                    (a: Omit<Ranking, 'displayIndex'>, b: Omit<Ranking, 'displayIndex'>) =>
+                      b.score_nm - a.score_nm || b.score_un - a.score_un || a.age - b.age
+                  )
+                  .map((item) => {
+                    return item.dod !== 1
+                      ? { ...item, displayIndex: indexCounter++ }
+                      : { ...item, displayIndex: -1 };
+                  })
+              : []
+          );
+
+          return;
+        }
+
         setMasterRanking(
-          JSON.parse(ranking).sort(
-            (a: Ranking, b: Ranking) =>
-              b.score_nm - a.score_nm || b.score_un - a.score_un || a.age - b.age
-          )
+          JSON.parse(ranking)
+            .sort(
+              (a: Ranking, b: Ranking) =>
+                b.score_nm - a.score_nm || b.score_un - a.score_un || a.age - b.age
+            )
+            .map((item: Ranking) => {
+              return item.dod !== 1
+                ? { ...item, displayIndex: indexCounter++ }
+                : { ...item, displayIndex: -1 };
+            })
         );
         setIsLoading(false);
       };
@@ -48,7 +76,19 @@ const MasterRankingScreen = () => {
 
   useEffect(() => {
     if (fullData) {
-      setFullRanking(fullData?.data?.sort((a: Ranking, b: Ranking) => b.score_nm - a.score_nm));
+      let indexCounter = 0;
+      setFullRanking(
+        fullData?.data
+          ?.sort(
+            (a: Omit<Ranking, 'displayIndex'>, b: Omit<Ranking, 'displayIndex'>) =>
+              b.score_nm - a.score_nm || b.score_un - a.score_un || a.age - b.age
+          )
+          .map((item) => {
+            return item.dod !== 1
+              ? { ...item, displayIndex: indexCounter++ }
+              : { ...item, displayIndex: -1 };
+          })
+      );
     }
   }, [fullData]);
 
@@ -93,7 +133,7 @@ const MasterRankingScreen = () => {
           <Profile
             userId={item.user_id}
             key={index}
-            index={index}
+            index={item.displayIndex}
             first_name={item.first_name}
             last_name={item.last_name}
             avatar={item.avatar}

+ 1 - 0
src/screens/InAppScreens/TravellersScreen/index.tsx

@@ -159,6 +159,7 @@ export interface Ranking {
   arrow_tbt: number;
   arrow_slow: number;
   arrow_kye: number;
+  displayIndex: number;
 }
 
 export default TravellersScreen;

+ 7 - 0
src/screens/InAppScreens/TravellersScreen/utils/sort.ts

@@ -8,6 +8,7 @@ export const applyModalSort = (
   country: filterCountryType
 ) => {
   let filteredLocalData = allRanking;
+  let indexCounter = 0;
 
   if (age) {
     filteredLocalData = filteredLocalData.filter(
@@ -129,5 +130,11 @@ export const applyModalSort = (
     }
   }
 
+  filteredLocalData = filteredLocalData.map((item) => {
+    return item.dod !== 1
+      ? { ...item, displayIndex: indexCounter++ }
+      : { ...item, displayIndex: -1 };
+  });
+
   return filteredLocalData;
 };

+ 140 - 0
src/screens/NotificationsScreen/FriendsNotificactionsScreen/index.tsx

@@ -0,0 +1,140 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, Switch, TouchableOpacity } from 'react-native';
+
+import { styles } from './styles';
+import { Colors } from 'src/theme';
+
+import { Header, PageWrapper } from 'src/components';
+import { usePostSetSettingsMutation } from '@api/notifications';
+
+const FriendsNotificationsScreen = ({ route }: { route: any }) => {
+  const [isAppFriendsSubscribed, setIsAppFriendsSubscribed] = useState(false);
+  const [isEmailFriendsSubscribed, setIsEmailFriendsSubscribed] = useState(false);
+  const settings = route.params?.settings;
+  const token = route.params?.token;
+  const { mutateAsync: setNotificationsSettings } = usePostSetSettingsMutation();
+
+  useEffect(() => {
+    const appFriendsSetting = settings.find((setting: any) => setting.name === 'app-friends');
+    const emailFriendsSetting = settings.find((setting: any) => setting.name === 'email-friends');
+
+    setIsAppFriendsSubscribed(appFriendsSetting?.active === 1);
+    setIsEmailFriendsSubscribed(emailFriendsSetting?.active === 1);
+  }, [settings]);
+
+  const handleToggleAppFriends = () => {
+    setIsAppFriendsSubscribed(!isAppFriendsSubscribed);
+    updateSettings('app-friends', isAppFriendsSubscribed ? 0 : 1);
+  };
+
+  const handleToggleEmailFriends = () => {
+    setIsEmailFriendsSubscribed(!isEmailFriendsSubscribed);
+    updateSettings('email-friends', isEmailFriendsSubscribed ? 0 : 1);
+  };
+
+  const updateSettings = async (settingName: string, active: number) => {
+    const dataToUpdate = { [settingName]: active };
+
+    await setNotificationsSettings({
+      token,
+      settings: JSON.stringify(dataToUpdate)
+    });
+  };
+
+  return (
+    <PageWrapper>
+      <Header label="Friends" />
+      <TouchableOpacity
+        style={[styles.alignStyle, styles.buttonWrapper, { justifyContent: 'space-between' }]}
+        onPress={handleToggleAppFriends}
+      >
+        <View style={styles.alignStyle}>
+          <Text
+            style={[
+              styles.buttonLabel,
+              !isAppFriendsSubscribed ? { color: Colors.LIGHT_GRAY } : {}
+            ]}
+          >
+            Friends notifications
+          </Text>
+        </View>
+        <View>
+          <Switch
+            trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
+            thumbColor={Colors.WHITE}
+            onValueChange={handleToggleAppFriends}
+            value={isAppFriendsSubscribed}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      </TouchableOpacity>
+
+      <TouchableOpacity
+        style={[styles.alignStyle, styles.buttonWrapper, { justifyContent: 'space-between' }]}
+        onPress={handleToggleEmailFriends}
+      >
+        <View style={styles.alignStyle}>
+          <Text
+            style={[
+              styles.buttonLabel,
+              !isEmailFriendsSubscribed ? { color: Colors.LIGHT_GRAY } : {}
+            ]}
+          >
+            Friends email notifications
+          </Text>
+        </View>
+        <View>
+          <Switch
+            trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
+            thumbColor={Colors.WHITE}
+            onValueChange={handleToggleEmailFriends}
+            value={isEmailFriendsSubscribed}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      </TouchableOpacity>
+
+      {/* <TouchableOpacity
+        style={[styles.alignStyle, styles.buttonWrapper, { justifyContent: 'space-between' }]}
+        onPress={() => {}}
+      >
+        <View style={styles.alignStyle}>
+          <Text style={[styles.buttonLabel, !isSubscribed ? { color: Colors.LIGHT_GRAY } : {}]}>
+            Rejected requests
+          </Text>
+        </View>
+        <View>
+          <Switch
+            trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
+            thumbColor={Colors.WHITE}
+            onValueChange={toggleSwitch}
+            value={isSubscribed}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      </TouchableOpacity> */}
+
+      {/* <TouchableOpacity
+        style={[styles.alignStyle, styles.buttonWrapper, { justifyContent: 'space-between' }]}
+        onPress={() => {}}
+      >
+        <View style={styles.alignStyle}>
+          <Text style={[styles.buttonLabel, !isSubscribed ? { color: Colors.LIGHT_GRAY } : {}]}>
+            Approved requests
+          </Text>
+        </View>
+        <View>
+          <Switch
+            trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
+            thumbColor={Colors.WHITE}
+            onValueChange={toggleSwitch}
+            value={isSubscribed}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      </TouchableOpacity> */}
+    </PageWrapper>
+  );
+};
+
+export default FriendsNotificationsScreen;

+ 20 - 0
src/screens/NotificationsScreen/FriendsNotificactionsScreen/styles.tsx

@@ -0,0 +1,20 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+import { getFontSize } from 'src/utils';
+
+export const styles = StyleSheet.create({
+  alignStyle: {
+    display: 'flex',
+    flexDirection: 'row',
+    alignItems: 'center'
+  },
+  buttonWrapper: {
+    width: '100%',
+    height: 48
+  },
+  buttonLabel: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontFamily: 'montserrat-700'
+  }
+});

+ 40 - 0
src/screens/NotificationsScreen/MessagesNotificationsScreen/index.tsx

@@ -0,0 +1,40 @@
+import React, { useState } from 'react';
+import { View, Text, Switch, TouchableOpacity } from 'react-native';
+
+import { styles } from './styles';
+import { Colors } from 'src/theme';
+
+import { Header, PageWrapper } from 'src/components';
+
+const MessagesNotificationsScreen = () => {
+  const [isSubscribed, setIsSubscribed] = useState(false);
+
+  const toggleSwitch = () => setIsSubscribed((prevState) => !prevState);
+
+  return (
+    <PageWrapper>
+      <Header label="Messages" />
+      <TouchableOpacity
+        style={[styles.alignStyle, styles.buttonWrapper, { justifyContent: 'space-between' }]}
+        onPress={() => {}}
+      >
+        <View style={styles.alignStyle}>
+          <Text style={[styles.buttonLabel, !isSubscribed ? { color: Colors.LIGHT_GRAY } : {}]}>
+            Message notifications
+          </Text>
+        </View>
+        <View>
+          <Switch
+            trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
+            thumbColor={Colors.WHITE}
+            onValueChange={toggleSwitch}
+            value={isSubscribed}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      </TouchableOpacity>
+    </PageWrapper>
+  );
+};
+
+export default MessagesNotificationsScreen;

+ 20 - 0
src/screens/NotificationsScreen/MessagesNotificationsScreen/styles.tsx

@@ -0,0 +1,20 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+import { getFontSize } from 'src/utils';
+
+export const styles = StyleSheet.create({
+  alignStyle: {
+    display: 'flex',
+    flexDirection: 'row',
+    alignItems: 'center'
+  },
+  buttonWrapper: {
+    width: '100%',
+    height: 48
+  },
+  buttonLabel: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontFamily: 'montserrat-700'
+  }
+});

+ 168 - 0
src/screens/NotificationsScreen/NotificationsListScreen/index.tsx

@@ -0,0 +1,168 @@
+import React, { useState } from 'react';
+import { View, Text, FlatList, TouchableOpacity } from 'react-native';
+
+import { Header, PageWrapper } from 'src/components';
+import Modal from 'react-native-modal';
+import { RectButton, Swipeable } from 'react-native-gesture-handler';
+import { styles } from './styles';
+import { Colors } from 'src/theme';
+
+import PeopleIcon from 'assets/icons/notifications/people-group-solid.svg';
+import ChatIcon from 'assets/icons/notifications/messages.svg';
+import GearIcon from 'assets/icons/notifications/gear-solid.svg';
+import TrashIcon from 'assets/icons/travels-screens/trash-solid.svg';
+
+const NotificationsListScreen = ({ navigation }: { navigation: any }) => {
+  const [notifications, setNotifications] = useState<Notification[]>([
+    {
+      id: '1',
+      title: 'New friend request',
+      description:
+        'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the indimply dummy text of the printing and typesetting industry',
+      date: 'Today, 09.09.2024',
+      icon: <PeopleIcon fill={Colors.WHITE} width={16} height={16} />,
+      modalContent: 'Hello! You have received a new friend request. Please take a look',
+      isRead: false,
+      largeIcon: <PeopleIcon fill={Colors.ORANGE} width={52} height={52} />
+    },
+    {
+      id: '2',
+      title: 'You have 3 messages',
+      description: 'Lorem Ipsum is simply dummy text of the',
+      date: 'Today, 09.09.2024',
+      icon: <ChatIcon fill={Colors.WHITE} width={16} height={16} />,
+      modalContent: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
+      isRead: false,
+      largeIcon: <ChatIcon fill={Colors.ORANGE} width={52} height={52} />
+    },
+    {
+      id: '3',
+      title: 'A new poll',
+      description:
+        'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the indimply dummy text of the printing and typesetting industry',
+      date: 'Yesterday, 08.09.2024',
+      icon: <GearIcon fill={Colors.DARK_BLUE} width={16} height={16} />,
+      modalContent: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
+      isRead: true,
+      largeIcon: <GearIcon fill={Colors.ORANGE} width={52} height={52} />
+    }
+  ]);
+
+  const [selectedNotification, setSelectedNotification] = useState<Notification | null>(null);
+  const [isModalVisible, setModalVisible] = useState(false);
+
+  const openModal = (notification: Notification) => {
+    setSelectedNotification(notification);
+    setModalVisible(true);
+    !notification.isRead && markAsRead(notification.id);
+  };
+
+  const closeModal = () => {
+    setModalVisible(false);
+    setSelectedNotification(null);
+  };
+
+  const markAsRead = (id: string) => {
+    setNotifications((prevNotifications) =>
+      prevNotifications.map((n) => (n.id === id ? { ...n, isRead: true } : n))
+    );
+  };
+
+  const deleteNotification = (id: string) => {
+    setNotifications((prevNotifications) => prevNotifications.filter((n) => n.id !== id));
+  };
+
+  const renderRightActions = (id: string) => (
+    <RectButton style={styles.deleteButton} onPress={() => deleteNotification(id)}>
+      <TrashIcon fill={Colors.RED} width={18} height={18} />
+      <Text style={styles.deleteButtonText}>Delete</Text>
+    </RectButton>
+  );
+
+  const renderNotificationItem = ({ item }: { item: Notification }) => (
+    <Swipeable renderRightActions={() => renderRightActions(item.id)}>
+      <TouchableOpacity
+        style={[
+          styles.notificationItem,
+          item.isRead ? styles.notificationRead : styles.notificationUnread
+        ]}
+        onPress={() => openModal(item)}
+      >
+        <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
+          <Text
+            style={[
+              styles.notificationTitle,
+              item.isRead ? { color: Colors.DARK_BLUE } : { color: Colors.WHITE }
+            ]}
+          >
+            {item.title}
+          </Text>
+          <View style={[styles.notificationIcon, item.isRead ? {} : {}]}>{item.icon}</View>
+        </View>
+
+        <Text
+          style={[
+            styles.notificationDescription,
+            item.isRead ? { color: Colors.DARK_BLUE } : { color: Colors.WHITE }
+          ]}
+          numberOfLines={2}
+        >
+          {item.description}
+        </Text>
+        <Text
+          style={[
+            styles.notificationDate,
+            item.isRead ? { color: Colors.DARK_BLUE } : { color: Colors.WHITE }
+          ]}
+        >
+          {item.date}
+        </Text>
+      </TouchableOpacity>
+    </Swipeable>
+  );
+
+  return (
+    <PageWrapper>
+      <Header label="Notifications list" />
+      <FlatList
+        data={notifications}
+        renderItem={renderNotificationItem}
+        keyExtractor={(item) => item.id}
+        contentContainerStyle={styles.notificationList}
+      />
+
+      {selectedNotification && (
+        <Modal
+          isVisible={isModalVisible}
+          onBackdropPress={closeModal}
+          onBackButtonPress={closeModal}
+          style={styles.modalWrapper}
+        >
+          <View style={styles.modalContainer}>
+            <View style={styles.modalIcon}>{selectedNotification.largeIcon}</View>
+            <View style={{ width: '100%', gap: 12, marginBottom: 52 }}>
+              <Text style={styles.modalTitle}>{selectedNotification.title}</Text>
+              <Text style={styles.modalDescription}>{selectedNotification.modalContent}</Text>
+              <Text style={styles.modalDate}>{selectedNotification.date}</Text>
+            </View>
+
+            <TouchableOpacity style={styles.modalButton} onPress={closeModal}>
+              <Text style={styles.modalButtonText}>OK</Text>
+            </TouchableOpacity>
+            <TouchableOpacity style={styles.modalActionButton}>
+              <Text style={styles.modalActionButtonText}>
+                {selectedNotification.title.includes('friend')
+                  ? 'Go to friends'
+                  : selectedNotification.title.includes('messages')
+                    ? 'Go to messages'
+                    : 'Go to polls'}
+              </Text>
+            </TouchableOpacity>
+          </View>
+        </Modal>
+      )}
+    </PageWrapper>
+  );
+};
+
+export default NotificationsListScreen;

+ 121 - 0
src/screens/NotificationsScreen/NotificationsListScreen/styles.tsx

@@ -0,0 +1,121 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  notificationList: {
+    gap: 12
+  },
+  notificationItem: {
+    borderWidth: 1,
+    borderRadius: 8,
+    padding: 12,
+    gap: 16,
+    flex: 1
+  },
+  notificationIcon: {},
+  notificationTextContainer: {
+    flex: 1,
+    gap: 12
+  },
+  notificationTitle: {
+    fontSize: 14,
+    fontFamily: 'montserrat-700',
+    color: Colors.DARK_BLUE,
+    flex: 1
+  },
+  notificationDescription: {
+    fontSize: 12,
+    fontWeight: '600',
+    color: Colors.DARK_BLUE
+  },
+  notificationDate: {
+    fontSize: 10,
+    fontWeight: '600',
+    color: Colors.DARK_BLUE,
+    textAlign: 'right'
+  },
+  deleteButton: {
+    borderWidth: 1,
+    borderColor: Colors.RED,
+    justifyContent: 'center',
+    alignItems: 'center',
+    width: 80,
+    borderRadius: 8,
+    gap: 12
+  },
+  deleteButtonText: {
+    color: Colors.RED,
+    fontWeight: '600',
+    fontSize: 12
+  },
+  modalWrapper: {
+    justifyContent: 'flex-end',
+    margin: 0
+  },
+  modalContainer: {
+    backgroundColor: Colors.WHITE,
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    padding: 16,
+    paddingBottom: 32,
+    alignItems: 'center',
+    width: '100%'
+  },
+  modalIcon: {
+    marginBottom: 16
+  },
+  modalTitle: {
+    fontSize: 14,
+    fontFamily: 'montserrat-700',
+    color: Colors.DARK_BLUE
+  },
+  modalDescription: {
+    fontSize: 12,
+    color: Colors.DARK_BLUE,
+    fontWeight: '600'
+  },
+  modalDate: {
+    fontSize: 10,
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    textAlign: 'right'
+  },
+  modalButton: {
+    backgroundColor: Colors.ORANGE,
+    paddingVertical: 12,
+    paddingHorizontal: 32,
+    borderRadius: 4,
+    marginBottom: 12,
+    borderWidth: 0.5,
+    borderColor: Colors.ORANGE,
+    width: '100%',
+    alignItems: 'center'
+  },
+  modalButtonText: {
+    fontSize: 15,
+    color: Colors.WHITE,
+    fontWeight: '700'
+  },
+  modalActionButton: {
+    paddingVertical: 12,
+    paddingHorizontal: 32,
+    borderRadius: 4,
+    borderWidth: 0.5,
+    borderColor: Colors.DARK_BLUE,
+    width: '100%',
+    alignItems: 'center'
+  },
+  modalActionButtonText: {
+    fontSize: 15,
+    color: Colors.DARK_BLUE,
+    fontWeight: '700'
+  },
+  notificationUnread: {
+    backgroundColor: Colors.ORANGE,
+    borderColor: Colors.ORANGE
+  },
+  notificationRead: {
+    backgroundColor: Colors.WHITE,
+    borderColor: Colors.DARK_BLUE
+  }
+});

+ 40 - 0
src/screens/NotificationsScreen/SystemNotificationsScreen/index.tsx

@@ -0,0 +1,40 @@
+import React, { useState } from 'react';
+import { View, Text, Switch, TouchableOpacity } from 'react-native';
+
+import { styles } from './styles';
+import { Colors } from 'src/theme';
+
+import { Header, PageWrapper } from 'src/components';
+
+const SystemNotificationsScreen = () => {
+  const [isSubscribed, setIsSubscribed] = useState(false);
+
+  const toggleSwitch = () => setIsSubscribed((prevState) => !prevState);
+
+  return (
+    <PageWrapper>
+      <Header label="System" />
+      <TouchableOpacity
+        style={[styles.alignStyle, styles.buttonWrapper, { justifyContent: 'space-between' }]}
+        onPress={() => {}}
+      >
+        <View style={styles.alignStyle}>
+          <Text style={[styles.buttonLabel, !isSubscribed ? { color: Colors.LIGHT_GRAY } : {}]}>
+            System notifications
+          </Text>
+        </View>
+        <View>
+          <Switch
+            trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
+            thumbColor={Colors.WHITE}
+            onValueChange={toggleSwitch}
+            value={isSubscribed}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      </TouchableOpacity>
+    </PageWrapper>
+  );
+};
+
+export default SystemNotificationsScreen;

+ 20 - 0
src/screens/NotificationsScreen/SystemNotificationsScreen/styles.tsx

@@ -0,0 +1,20 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+import { getFontSize } from 'src/utils';
+
+export const styles = StyleSheet.create({
+  alignStyle: {
+    display: 'flex',
+    flexDirection: 'row',
+    alignItems: 'center'
+  },
+  buttonWrapper: {
+    width: '100%',
+    height: 48
+  },
+  buttonLabel: {
+    color: Colors.DARK_BLUE,
+    fontSize: getFontSize(14),
+    fontFamily: 'montserrat-700'
+  }
+});

+ 238 - 0
src/screens/NotificationsScreen/index.tsx

@@ -0,0 +1,238 @@
+import React, { useEffect, useState } from 'react';
+import { View, Linking, Text, Switch, Platform, TouchableOpacity } from 'react-native';
+import * as Notifications from 'expo-notifications';
+
+import { Header, Loading, MenuButton, PageWrapper, WarningModal } from 'src/components';
+import { usePushNotification } from 'src/contexts/PushNotificationContext';
+import { usePostSaveNotificationTokenMutation } from '@api/user';
+import { styles } from 'src/components/MenuButton/style';
+import { StoreType, storage } from 'src/storage';
+import { Colors } from 'src/theme';
+import { NAVIGATION_PAGES } from 'src/types';
+
+import ListIcon from 'assets/icons/notifications/list-check.svg';
+import PeopleIcon from 'assets/icons/notifications/people-group-solid.svg';
+import ChatIcon from 'assets/icons/notifications/messages.svg';
+import GearIcon from 'assets/icons/notifications/gear-solid.svg';
+import BellIcon from 'assets/icons/notifications/bell-solid.svg';
+import { useGetSettingsQuery } from '@api/notifications';
+import { useFocusEffect } from '@react-navigation/native';
+
+const NotificationsScreen = ({ navigation }: { navigation: any }) => {
+  const token = storage.get('token', StoreType.STRING) as string;
+  const { data: notificationsSettings, refetch } = useGetSettingsQuery(token, !!token);
+
+  const { mutateAsync: saveNotificationToken } = usePostSaveNotificationTokenMutation();
+  const { isSubscribed, toggleSubscription } = usePushNotification();
+  const [modalInfo, setModalInfo] = useState({
+    visible: false,
+    type: 'confirm',
+    message: '',
+    action: () => {}
+  });
+  const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState(false);
+
+  const closeModal = () => {
+    setModalInfo({ ...modalInfo, visible: false });
+  };
+
+  useEffect(() => {
+    if (notificationsSettings) {
+      const { settings } = notificationsSettings;
+      const isServerSubscribed =
+        Platform.OS === 'ios'
+          ? settings.some((setting) => setting.name === 'app-ios' && setting.active === 1)
+          : settings.some((setting) => setting.name === 'app-android' && setting.active === 1);
+      // if (isServerSubscribed !== isSubscribed) {
+      //   toggleSubscription();
+      // }
+    }
+  }, [notificationsSettings]);
+
+  useFocusEffect(() => {
+    refetchData();
+  });
+
+  const refetchData = async () => {
+    await refetch();
+  };
+
+  if (!notificationsSettings) return <Loading />;
+
+  const toggleSwitch = async () => {
+    if (isSubscribed) {
+      toggleSubscription();
+    } else {
+      const { status } = await Notifications.getPermissionsAsync();
+      if (status !== 'granted') {
+        setModalInfo({
+          visible: true,
+          type: 'success',
+          message:
+            'To use this feature we need your permission to access your notifications. If you press OK your system will ask you to confirm permission to receive notifications from NomadMania.',
+          action: () => setShouldOpenWarningModal(true)
+        });
+      } else {
+        handleSubscribe();
+      }
+    }
+  };
+
+  const handleSubscribe = async () => {
+    const deviceData = await registerForPushNotificationsAsync();
+
+    if (deviceData?.notificationToken) {
+      toggleSubscription();
+      await saveNotificationToken({
+        token,
+        platform: deviceData.platform,
+        n_token: deviceData.notificationToken
+      });
+      refetchData();
+    }
+  };
+
+  async function registerForPushNotificationsAsync() {
+    const { status: existingStatus } = await Notifications.getPermissionsAsync();
+    let finalStatus = existingStatus;
+    if (existingStatus !== 'granted') {
+      const { status } = await Notifications.requestPermissionsAsync();
+      finalStatus = status;
+    }
+    if (finalStatus !== 'granted') {
+      setModalInfo({
+        visible: true,
+        type: 'success',
+        message:
+          'NomadMania app needs notification permissions to function properly. Open settings?',
+        action: () =>
+          Platform.OS === 'ios' ? Linking.openURL('app-settings:') : Linking.openSettings()
+      });
+      return null;
+    }
+    const deviceData = await Notifications.getDevicePushTokenAsync();
+    console.log('deviceData', deviceData);
+
+    if (Platform.OS === 'android') {
+      Notifications.setNotificationChannelAsync('default', {
+        name: 'default',
+        importance: Notifications.AndroidImportance.MAX,
+        vibrationPattern: [0, 250, 250, 250],
+        lightColor: '#FF231F7C'
+      });
+    }
+    storage.set('deviceToken', deviceData.data);
+
+    return { notificationToken: deviceData.data ?? '', platform: deviceData.type ?? '' };
+  }
+
+  return (
+    <PageWrapper>
+      <Header label="Notifications" />
+      <TouchableOpacity
+        style={[
+          styles.alignStyle,
+          styles.buttonWrapper,
+          {
+            justifyContent: 'space-between'
+          }
+        ]}
+        onPress={toggleSwitch}
+      >
+        <View style={styles.alignStyle}>
+          <BellIcon fill={Colors.DARK_BLUE} width={20} height={20} />
+          <Text style={styles.buttonLabel}>Enable notifications</Text>
+        </View>
+        <View>
+          <Switch
+            trackColor={{ false: Colors.LIGHT_GRAY, true: Colors.DARK_BLUE }}
+            thumbColor={Colors.WHITE}
+            onValueChange={toggleSwitch}
+            value={isSubscribed}
+            style={{ transform: 'scale(0.8)' }}
+          />
+        </View>
+      </TouchableOpacity>
+
+      {/* <MenuButton
+        label="Notifications list"
+        icon={
+          <ListIcon
+            fill={isSubscribed ? Colors.DARK_BLUE : Colors.LIGHT_GRAY}
+            width={20}
+            height={20}
+          />
+        }
+        red={false}
+        buttonFn={() => navigation.navigate(NAVIGATION_PAGES.NOTIFICATIONS_LIST as never)}
+        disabled={!isSubscribed}
+      /> */}
+
+      <MenuButton
+        label="Friends"
+        icon={
+          <PeopleIcon
+            fill={isSubscribed ? Colors.DARK_BLUE : Colors.LIGHT_GRAY}
+            width={20}
+            height={20}
+          />
+        }
+        red={false}
+        buttonFn={() =>
+          navigation.navigate(NAVIGATION_PAGES.FRIENDS_NOTIFICATIONS, {
+            settings: notificationsSettings.settings,
+            token
+          } as never)
+        }
+        disabled={!isSubscribed}
+      />
+
+      {/* <MenuButton
+        label="Messages"
+        icon={
+          <ChatIcon
+            fill={isSubscribed ? Colors.DARK_BLUE : Colors.LIGHT_GRAY}
+            width={20}
+            height={20}
+          />
+        }
+        red={false}
+        buttonFn={() => navigation.navigate(NAVIGATION_PAGES.MESSAGES_NOTIFICATIONS as never)}
+        disabled={!isSubscribed}
+      /> */}
+
+      {/* <MenuButton
+        label="System"
+        icon={
+          <GearIcon
+            fill={isSubscribed ? Colors.DARK_BLUE : Colors.LIGHT_GRAY}
+            width={20}
+            height={20}
+          />
+        }
+        red={false}
+        buttonFn={() => navigation.navigate(NAVIGATION_PAGES.SYSTEM_NOTIFICATIONS as never)}
+        disabled={!isSubscribed}
+      /> */}
+
+      <WarningModal
+        isVisible={modalInfo.visible}
+        onClose={closeModal}
+        type={modalInfo.type}
+        message={modalInfo.message}
+        action={() => {
+          modalInfo.action();
+          closeModal();
+        }}
+        onModalHide={() => {
+          if (shouldOpenWarningModal) {
+            setShouldOpenWarningModal(false);
+            handleSubscribe();
+          }
+        }}
+      />
+    </PageWrapper>
+  );
+};
+
+export default NotificationsScreen;

+ 8 - 3
src/types/api.ts

@@ -20,7 +20,8 @@ export enum API_ROUTE {
   PROFILE = 'profile',
   FRIENDS = 'friends',
   COUNTRIES = 'countries',
-  FIXERS = 'fixers'
+  FIXERS = 'fixers',
+  NOTIFICATIONS = 'notifications'
 }
 
 export enum API_ENDPOINT {
@@ -120,7 +121,9 @@ export enum API_ENDPOINT {
   SAVE_RATING = 'save-rating-app',
   ADD_FIXER = 'add-fixer',
   EDIT_FIXER = 'edit-fixer',
-  GET_UPDATE = 'get-update'
+  GET_UPDATE = 'get-update',
+  GET_NOTIFICATIONS_SETTINGS = 'get-settings',
+  SET_NOTIFICATIONS_SETTINGS = 'set-settings'
 }
 
 export enum API {
@@ -219,7 +222,9 @@ export enum API {
   SAVE_RATING = `${API_ROUTE.FIXERS}/${API_ENDPOINT.SAVE_RATING}`,
   ADD_FIXER = `${API_ROUTE.FIXERS}/${API_ENDPOINT.ADD_FIXER}`,
   EDIT_FIXER = `${API_ROUTE.FIXERS}/${API_ENDPOINT.EDIT_FIXER}`,
-  GET_UPDATE = `${API_ROUTE.PROFILE}/${API_ENDPOINT.GET_UPDATE}`
+  GET_UPDATE = `${API_ROUTE.PROFILE}/${API_ENDPOINT.GET_UPDATE}`,
+  GET_NOTIFICATIONS_SETTINGS = `${API_ROUTE.NOTIFICATIONS}/${API_ENDPOINT.GET_NOTIFICATIONS_SETTINGS}`,
+  SET_NOTIFICATIONS_SETTINGS = `${API_ROUTE.NOTIFICATIONS}/${API_ENDPOINT.SET_NOTIFICATIONS_SETTINGS}`
 }
 
 export type BaseAxiosError = AxiosError;

+ 5 - 0
src/types/navigation.ts

@@ -62,4 +62,9 @@ export enum NAVIGATION_PAGES {
   ADD_FIXER = 'inAppAddFixer',
   FIXERS_COMMENTS = 'inAppFixersComments',
   SHARE_PROFILE = 'inAppShareProfile',
+  NOTIFICATIONS = 'inAppNotifications',
+  NOTIFICATIONS_LIST = 'inAppNotificationsList',
+  FRIENDS_NOTIFICATIONS = 'inAppFriendsNotifications',
+  MESSAGES_NOTIFICATIONS = 'inAppMessagesNotifications',
+  SYSTEM_NOTIFICATIONS = 'inAppSystemNotifications',
 }

Деякі файли не було показано, через те що забагато файлів було змінено