Viktoriia 5 ماه پیش
والد
کامیت
0fe0a7bf90

+ 10 - 10
app.config.ts

@@ -70,7 +70,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
       googleMapsApiKey: env.IOS_GOOGLE_MAP_APIKEY
     },
     infoPlist: {
-      UIBackgroundModes: ['location', 'fetch'],
+      UIBackgroundModes: ['fetch'],
       NSLocationAlwaysUsageDescription:
         'Turn on location service to allow NomadMania.com find friends nearby.',
       NSPhotoLibraryUsageDescription:
@@ -105,7 +105,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
     },
     googleServicesFile: './google-services.json',
     permissions: [
-      'ACCESS_BACKGROUND_LOCATION',
+      // 'ACCESS_BACKGROUND_LOCATION',
       'ACCESS_FINE_LOCATION',
       'ACCESS_COARSE_LOCATION',
       'READ_EXTERNAL_STORAGE',
@@ -161,13 +161,13 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
         microphonePermission: 'Allow Nomadmania to access your microphone.'
       }
     ],
-    ['@maplibre/maplibre-react-native'],
-    [
-      'expo-location',
-      {
-        isIosBackgroundLocationEnabled: true,
-        isAndroidBackgroundLocationEnabled: true
-      }
-    ]
+    ['@maplibre/maplibre-react-native']
+    // [
+    //   'expo-location',
+    //   {
+    //     isIosBackgroundLocationEnabled: true,
+    //     isAndroidBackgroundLocationEnabled: true
+    //   }
+    // ]
   ]
 });

+ 10 - 0
assets/icons/messages/megaphone.svg

@@ -0,0 +1,10 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4475_38582)">
+<path d="M16.875 1.125C16.875 0.671488 16.6008 0.26016 16.1789 0.084379C15.757 -0.0914022 15.2754 0.00703526 14.952 0.326957L13.4191 1.86329C11.7316 3.55079 9.44297 4.5 7.05586 4.5H6.75H5.625H2.25C1.00898 4.5 0 5.50899 0 6.75V10.125C0 11.366 1.00898 12.375 2.25 12.375V16.875C2.25 17.4973 2.75273 18 3.375 18H5.625C6.24727 18 6.75 17.4973 6.75 16.875V12.375H7.05586C9.44297 12.375 11.7316 13.3242 13.4191 15.0117L14.952 16.5445C15.2754 16.868 15.757 16.9629 16.1789 16.7871C16.6008 16.6113 16.875 16.2035 16.875 15.7465V10.5574C17.5289 10.2481 18 9.41485 18 8.43399C18 7.45313 17.5289 6.61993 16.875 6.31055V1.125ZM14.625 3.82149V8.4375V13.0535C12.5578 11.1727 9.86133 10.125 7.05586 10.125H6.75V6.75H7.05586C9.86133 6.75 12.5578 5.70235 14.625 3.82149Z" fill="#EF5B5B"/>
+</g>
+<defs>
+<clipPath id="clip0_4475_38582">
+<rect width="18" height="18" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 8 - 1
src/modules/api/chat/chat-api.ts

@@ -97,6 +97,11 @@ export interface PostDeleteChat {
   conversation_with_user: number;
 }
 
+export interface PostReportConversation {
+  token: string;
+  reported_user_id: number;
+}
+
 export interface PostSetSettings {
   token: string;
   value: 0 | 1;
@@ -154,5 +159,7 @@ export const chatApi = {
   getUnreadMessagesCount: (token: string) =>
     request.postForm<PostGetUnreadCountReturn>(API.GET_UNREAD_MESSAGES_PRESENT, {
       token
-    })
+    }),
+  reportConversation: (data: PostReportConversation) =>
+    request.postForm<ResponseType>(API.REPORT_CONVERSATION, data)
 };

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

@@ -19,5 +19,6 @@ export const chatQueryKeys = {
   deleteChat: () => ['deleteChat'] as const,
   unreactToMessage: () => ['unreactToMessage'] as const,
   getBlocked: (token: string) => ['getBlocked', token] as const,
-  getUnreadMessagesCount: (token: string) => ['getUnreadMessagesCount', token] as const
+  getUnreadMessagesCount: (token: string) => ['getUnreadMessagesCount', token] as const,
+  reportConversation: () => ['reportConversation'] as const
 };

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

@@ -14,3 +14,4 @@ export * from './use-post-delete-conversation';
 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';

+ 17 - 0
src/modules/api/chat/queries/use-post-report-conversation.tsx

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

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

@@ -83,10 +83,10 @@ import MapButton from 'src/components/MapButton';
 import { useAvatarStore } from 'src/stores/avatarVersionStore';
 import _ from 'lodash';
 import ScaleBar from 'src/components/ScaleBar';
-import {
-  startBackgroundLocationUpdates,
-  stopBackgroundLocationUpdates
-} from 'src/utils/backgroundLocation';
+// import {
+//   startBackgroundLocationUpdates,
+//   stopBackgroundLocationUpdates
+// } from 'src/utils/backgroundLocation';
 
 const defaultUserAvatar = require('assets/icon-user-share-location-solid.png');
 const logo = require('assets/logo-ua.png');
@@ -625,23 +625,23 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
       ) {
         setShowNomads(false);
         storage.set('showNomads', false);
-        await stopBackgroundLocationUpdates();
+        // await stopBackgroundLocationUpdates();
         return;
       }
 
-      const bgStatus = await Location.getBackgroundPermissionsAsync();
-      if (bgStatus.status !== 'granted') {
-        const { status } = await Location.requestBackgroundPermissionsAsync();
-        if (status === Location.PermissionStatus.GRANTED) {
-          await startBackgroundLocationUpdates();
-          console.log('[Permissions] Background granted');
-        } else {
-          console.log('[Permissions] Background denied');
-        }
-      } else {
-        await startBackgroundLocationUpdates();
-        console.log('[Permissions] Background already granted');
-      }
+      // const bgStatus = await Location.getBackgroundPermissionsAsync();
+      // if (bgStatus.status !== 'granted') {
+      //   const { status } = await Location.requestBackgroundPermissionsAsync();
+      //   if (status === Location.PermissionStatus.GRANTED) {
+      //     await startBackgroundLocationUpdates();
+      //     console.log('[Permissions] Background granted');
+      //   } else {
+      //     console.log('[Permissions] Background denied');
+      //   }
+      // } else {
+      //   await startBackgroundLocationUpdates();
+      //   console.log('[Permissions] Background already granted');
+      // }
 
       try {
         let currentLocation = await Location.getCurrentPositionAsync({
@@ -913,19 +913,19 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
 
       if (status === 'granted' && isServicesEnabled) {
         await getLocation();
-        const bgStatus = await Location.getBackgroundPermissionsAsync();
-        if (bgStatus.status !== 'granted') {
-          const { status } = await Location.requestBackgroundPermissionsAsync();
-          if (status === Location.PermissionStatus.GRANTED) {
-            await startBackgroundLocationUpdates();
-            console.log('[Permissions] Background granted');
-          } else {
-            console.log('[Permissions] Background denied');
-          }
-        } else {
-          await startBackgroundLocationUpdates();
-          console.log('[Permissions] Background already granted');
-        }
+        // const bgStatus = await Location.getBackgroundPermissionsAsync();
+        // if (bgStatus.status !== 'granted') {
+        //   const { status } = await Location.requestBackgroundPermissionsAsync();
+        //   if (status === Location.PermissionStatus.GRANTED) {
+        //     await startBackgroundLocationUpdates();
+        //     console.log('[Permissions] Background granted');
+        //   } else {
+        //     console.log('[Permissions] Background denied');
+        //   }
+        // } else {
+        //   await startBackgroundLocationUpdates();
+        //   console.log('[Permissions] Background already granted');
+        // }
       } else if (!canAskAgain || !isServicesEnabled) {
         setOpenSettingsVisible(true);
       } else {

+ 25 - 9
src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx

@@ -24,7 +24,8 @@ import {
   BubbleProps,
   Composer,
   TimeProps,
-  MessageProps
+  MessageProps,
+  Actions
 } from 'react-native-gifted-chat';
 import { MaterialCommunityIcons } from '@expo/vector-icons';
 import { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';
@@ -63,6 +64,7 @@ import { dismissChatNotifications } from '../utils';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 
 import BanIcon from 'assets/icons/messages/ban.svg';
+import AttachmentsModal from '../Components/AttachmentsModal';
 
 const options = {
   enableVibrateFallback: true,
@@ -112,7 +114,9 @@ const ChatScreen = ({ route }: { route: any }) => {
     visible: false,
     type: 'confirm',
     message: '',
-    action: () => {}
+    action: () => {},
+    buttonTitle: '',
+    title: ''
   });
 
   const [selectedMessage, setSelectedMessage] = useState<BubbleProps<CustomMessage> | null>(null);
@@ -1127,16 +1131,25 @@ const ChatScreen = ({ route }: { route: any }) => {
     );
   };
 
+  const openAttachmentsModal = () => {
+    SheetManager.show('chat-attachments', {
+      payload: {
+        name: userName,
+        uid: id,
+        setModalInfo
+      } as any
+    });
+  };
+
   const renderInputToolbar = (props: any) => (
     <InputToolbar
       {...props}
-      renderActions={() =>
-        // <Actions
-        //   icon={() => <MaterialCommunityIcons name="plus" size={28} color={Colors.DARK_BLUE} />}
-        //   // onPressActionButton={openActionSheet}
-        // />
-        null
-      }
+      renderActions={() => (
+        <Actions
+          icon={() => <MaterialCommunityIcons name="plus" size={28} color={Colors.DARK_BLUE} />}
+          onPressActionButton={openAttachmentsModal}
+        />
+      )}
       containerStyle={{
         backgroundColor: Colors.FILL_LIGHT
       }}
@@ -1373,11 +1386,14 @@ const ChatScreen = ({ route }: { route: any }) => {
           onClose={closeModal}
           type={modalInfo.type}
           message={modalInfo.message}
+          buttonTitle={modalInfo.buttonTitle}
+          title={modalInfo.title}
           action={() => {
             modalInfo.action();
             closeModal();
           }}
         />
+        <AttachmentsModal />
         <ReactionsListModal />
       </GestureHandlerRootView>
       <View

+ 108 - 0
src/screens/InAppScreens/MessagesScreen/Components/AttachmentsModal.tsx

@@ -0,0 +1,108 @@
+import React, { useState } from 'react';
+import { StyleSheet, TouchableOpacity, View, Text } from 'react-native';
+import ActionSheet, { SheetManager } from 'react-native-actions-sheet';
+import { getFontSize } from 'src/utils';
+import { Colors } from 'src/theme';
+import { WarningProps } from '../types';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import { usePostReportConversationMutation } from '@api/chat';
+
+import MegaphoneIcon from 'assets/icons/messages/megaphone.svg';
+
+const AttachmentsModal = () => {
+  const insets = useSafeAreaInsets();
+  const [chatData, setChatData] = useState<any>(null);
+  const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState<WarningProps | null>(null);
+  const { mutateAsync: reportUser } = usePostReportConversationMutation();
+
+  const handleSheetOpen = (payload: any) => {
+    setChatData(payload);
+  };
+
+  const handleReport = async () => {
+    if (!chatData) return;
+
+    setShouldOpenWarningModal({
+      title: `Report ${chatData.name}`,
+      buttonTitle: 'Report',
+      message: `Are you sure you want to report ${chatData.name}?\nIf you proceed, the chat history with ${chatData.name} will become visible to NomadMania admins for investigation.`,
+      action: async () => {
+        await reportUser({
+          token: chatData.token,
+          reported_user_id: chatData.uid
+        });
+      }
+    });
+
+    setTimeout(() => {
+      SheetManager.hide('chat-attachments');
+      setShouldOpenWarningModal(null);
+    }, 300);
+  };
+
+  return (
+    <ActionSheet
+      id="chat-attachments"
+      gestureEnabled={true}
+      containerStyle={{
+        borderTopLeftRadius: 15,
+        borderTopRightRadius: 15
+      }}
+      defaultOverlayOpacity={0.5}
+      onBeforeShow={(sheetRef) => {
+        const payload = sheetRef || null;
+        handleSheetOpen(payload);
+      }}
+      onClose={() => {
+        if (shouldOpenWarningModal) {
+          chatData?.setModalInfo({
+            visible: true,
+            type: 'delete',
+            title: shouldOpenWarningModal.title,
+            buttonTitle: shouldOpenWarningModal.buttonTitle,
+            message: shouldOpenWarningModal.message,
+            action: shouldOpenWarningModal.action
+          });
+        }
+      }}
+    >
+      <View
+        style={{
+          backgroundColor: 'white',
+          paddingHorizontal: 16,
+          gap: 16,
+          paddingTop: 8,
+          paddingBottom: 8 + insets.bottom
+        }}
+      >
+        <TouchableOpacity style={[styles.option, styles.dangerOption]} onPress={handleReport}>
+          <Text style={[styles.optionText, styles.dangerText]}>Report {chatData?.name}</Text>
+          <MegaphoneIcon fill={Colors.RED} />
+        </TouchableOpacity>
+      </View>
+    </ActionSheet>
+  );
+};
+
+const styles = StyleSheet.create({
+  option: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between'
+  },
+  optionText: {
+    fontSize: getFontSize(12),
+    fontWeight: '600',
+    color: Colors.DARK_BLUE
+  },
+  dangerOption: {
+    paddingVertical: 10,
+    borderBottomWidth: 1,
+    borderBlockColor: Colors.WHITE
+  },
+  dangerText: {
+    color: Colors.RED
+  }
+});
+
+export default AttachmentsModal;

+ 29 - 0
src/screens/InAppScreens/MessagesScreen/Components/MoreModal.tsx

@@ -10,6 +10,7 @@ import { useNavigation } from '@react-navigation/native';
 import { NAVIGATION_PAGES } from 'src/types';
 import {
   usePostDeleteChatMutation,
+  usePostReportConversationMutation,
   usePostSetBlockMutation,
   usePostSetMuteMutation
 } from '@api/chat';
@@ -18,6 +19,7 @@ import TrashIcon from 'assets/icons/travels-screens/trash-solid.svg';
 import BanIcon from 'assets/icons/messages/ban.svg';
 import BellSlashIcon from 'assets/icons/messages/bell-slash.svg';
 import { AvatarWithInitials } from 'src/components';
+import MegaphoneIcon from 'assets/icons/messages/megaphone.svg';
 
 const MoreModal = () => {
   const insets = useSafeAreaInsets();
@@ -36,6 +38,7 @@ const MoreModal = () => {
   const { mutateAsync: muteUser } = usePostSetMuteMutation();
   const { mutateAsync: blockUser } = usePostSetBlockMutation();
   const { mutateAsync: deleteChat } = usePostDeleteChatMutation();
+  const { mutateAsync: reportUser } = usePostReportConversationMutation();
 
   const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState<WarningProps | null>(null);
 
@@ -101,6 +104,27 @@ const MoreModal = () => {
     }, 300);
   };
 
+  const handleReport = async () => {
+    if (!chatData) return;
+
+    setShouldOpenWarningModal({
+      title: `Report ${name}`,
+      buttonTitle: 'Report',
+      message: `Are you sure you want to report ${name}?\nIf you proceed, the chat history with ${name} will become visible to NomadMania admins for investigation.`,
+      action: async () => {
+        await reportUser({
+          token: chatData.token,
+          reported_user_id: chatData.uid
+        });
+      }
+    });
+
+    setTimeout(() => {
+      SheetManager.hide('more-modal');
+      setShouldOpenWarningModal(null);
+    }, 300);
+  };
+
   const handleDelete = async () => {
     if (!chatData) return;
 
@@ -184,6 +208,11 @@ const MoreModal = () => {
           </View>
 
           <View style={[styles.optionsContainer, { paddingVertical: 0, gap: 0 }]}>
+            <TouchableOpacity style={[styles.option, styles.dangerOption]} onPress={handleReport}>
+              <Text style={[styles.optionText, styles.dangerText]}>Report {name}</Text>
+              <MegaphoneIcon fill={Colors.RED} />
+            </TouchableOpacity>
+
             <TouchableOpacity style={[styles.option, styles.dangerOption]} onPress={handleBlock}>
               <Text style={[styles.optionText, styles.dangerText]}>Block {name}</Text>
               <BanIcon fill={Colors.RED} />

+ 2 - 0
src/types/api.ts

@@ -159,6 +159,7 @@ export enum API_ENDPOINT {
   GET_USERS_LOCATION = 'get-users-location',
   IS_FEATURE_ACTIVE = 'is-feature-active',
   AUTHENTICATE = 'authenticate',
+  REPORT_CONVERSATION = 'report-conversation',
 }
 
 export enum API {
@@ -291,6 +292,7 @@ export enum API {
   GET_USERS_LOCATION = `${API_ROUTE.LOCATION}/${API_ENDPOINT.GET_USERS_LOCATION}`,
   IS_FEATURE_ACTIVE = `${API_ROUTE.LOCATION}/${API_ENDPOINT.IS_FEATURE_ACTIVE}`,
   AUTHENTICATE = `${API_ROUTE.USER}/${API_ENDPOINT.AUTHENTICATE}`,
+  REPORT_CONVERSATION = `${API_ROUTE.CHAT}/${API_ENDPOINT.REPORT_CONVERSATION}`,
 }
 
 export type BaseAxiosError = AxiosError;

+ 39 - 40
src/utils/backgroundLocation.ts

@@ -8,47 +8,47 @@ import { API } from 'src/types';
 
 const LOCATION_TASK_NAME = 'BACKGROUND_LOCATION_TASK';
 
-TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
-  if (error) {
-    console.error('[BackgroundLocation] Task error:', error);
-    return;
-  }
-  if (data) {
-    const { locations } = data as any;
-    if (locations && locations.length > 0) {
-      const { coords, timestamp } = locations[0];
+// TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
+//   if (error) {
+//     console.error('[BackgroundLocation] Task error:', error);
+//     return;
+//   }
+//   if (data) {
+//     const { locations } = data as any;
+//     if (locations && locations.length > 0) {
+//       const { coords, timestamp } = locations[0];
 
-      console.log('[BackgroundLocation] New location:', coords);
-      const token = storage.get('token', StoreType.STRING);
+//       console.log('[BackgroundLocation] New location:', coords);
+//       const token = storage.get('token', StoreType.STRING);
 
-      if (!token) {
-        return;
-      }
+//       if (!token) {
+//         return;
+//       }
 
-      try {
-        const response = await axios.postForm(
-          API_URL + '/' + API.UPDATE_LOCATION,
-          {
-            token,
-            lat: coords.latitude,
-            lng: coords.longitude,
-            background: LOCATION_TASK_NAME,
-            location_timestamp: timestamp
-          },
-          {
-            headers: {
-              Platform: Platform.OS
-            }
-          }
-        );
-        console.log('[BackgroundLocation] Location sent:', response.data);
-      } catch (sendError) {
-        console.error('[BackgroundLocation] Sending location failed:', sendError);
-      }
-    }
-  }
-  console.log('[BackgroundLocation] TaskManager end');
-});
+//       try {
+//         const response = await axios.postForm(
+//           API_URL + '/' + API.UPDATE_LOCATION,
+//           {
+//             token,
+//             lat: coords.latitude,
+//             lng: coords.longitude,
+//             background: LOCATION_TASK_NAME,
+//             location_timestamp: timestamp
+//           },
+//           {
+//             headers: {
+//               Platform: Platform.OS
+//             }
+//           }
+//         );
+//         console.log('[BackgroundLocation] Location sent:', response.data);
+//       } catch (sendError) {
+//         console.error('[BackgroundLocation] Sending location failed:', sendError);
+//       }
+//     }
+//   }
+//   console.log('[BackgroundLocation] TaskManager end');
+// });
 
 export const startBackgroundLocationUpdates = async () => {
   const hasStarted = await Location.hasStartedLocationUpdatesAsync(LOCATION_TASK_NAME);
@@ -61,8 +61,7 @@ export const startBackgroundLocationUpdates = async () => {
     accuracy: Location.Accuracy.Highest,
     // 10 minutes for Android
     timeInterval: 10 * 60 * 1000,
-    // just for testing on iOS
-    showsBackgroundLocationIndicator: true,
+    showsBackgroundLocationIndicator: false,
     pausesUpdatesAutomatically: false,
     // banner on Android
     foregroundService: {