소스 검색

added apis and delete func

Viktoriia 9 달 전
부모
커밋
1ab169fcb0

+ 29 - 4
src/modules/api/chat/chat-api.ts

@@ -11,7 +11,7 @@ export interface PostSearchUsersReturn extends ResponseType {
   }[];
 }
 
-// status: 1 -sent; 2 -received; 3 -read
+// status: 1 -sent; 2 -received; 3 -read; 4 - deleted
 export interface PostGetChatsListReturn extends ResponseType {
   conversations: {
     uid: number;
@@ -20,7 +20,7 @@ export interface PostGetChatsListReturn extends ResponseType {
     short: string;
     sent_by: number;
     updated: Date;
-    status: 1 | 2 | 3;
+    status: 1 | 2 | 3 | 4;
     unread_count: number;
     last_message_id: number;
     pin: 0 | 1;
@@ -37,7 +37,7 @@ interface Message {
   sender: number;
   recipient: number;
   text: string;
-  status: 1 | 2 | 3;
+  status: 1 | 2 | 3 | 4;
   sent_datetime: Date;
   received_datetime: Date | null;
   read_datetime: Date | null;
@@ -59,6 +59,23 @@ export interface PostSendMessage {
   text: string;
 }
 
+export interface PostMessagesReceivedOrRead {
+  token: string;
+  from_user: number;
+  messages_id: number[];
+}
+
+export interface PostReactToMessage {
+  message_id: number;
+  reaction: string;
+  conversation_with_user: number;
+}
+
+export interface PostDeleteMessage {
+  message_id: number;
+  conversation_with_user: number;
+}
+
 export const chatApi = {
   searchUsers: (token: string, search: string) =>
     request.postForm<PostSearchUsersReturn>(API.SEARCH_USERS, { token, search }),
@@ -76,5 +93,13 @@ export const chatApi = {
       no_of_messages,
       previous_than_message_id
     }),
-  sendMessage: (data: PostSendMessage) => request.postForm<ResponseType>(API.SEND_MESSAGE, data)
+  sendMessage: (data: PostSendMessage) => request.postForm<ResponseType>(API.SEND_MESSAGE, data),
+  messagesReceived: (data: PostMessagesReceivedOrRead) =>
+    request.postForm<ResponseType>(API.MESSAGES_RECEIVED, data),
+  messagesRead: (data: PostMessagesReceivedOrRead) =>
+    request.postForm<ResponseType>(API.MESSAGES_READ, data),
+  reactToMessage: (data: PostReactToMessage) =>
+    request.postForm<ResponseType>(API.REACT_TO_MESSAGE, data),
+  deleteMessage: (data: PostDeleteMessage) =>
+    request.postForm<ResponseType>(API.DELETE_MESSAGE, data)
 };

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

@@ -7,5 +7,9 @@ export const chatQueryKeys = {
     no_of_messages: number,
     previous_than_message_id: number
   ) => ['getChatWith', token, uid, no_of_messages, previous_than_message_id] as const,
-  sendMessage: () => ['sendMessage'] as const
+  sendMessage: () => ['sendMessage'] as const,
+  messagesReceived: () => ['messagesReceived'] as const,
+  messagesRead: () => ['messagesRead'] as const,
+  reactToMessage: () => ['reactToMessage'] as const,
+  deleteMessage: () => ['deleteMessage'] as const
 };

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

@@ -2,3 +2,7 @@ export * from './use-post-search-users';
 export * from './use-post-get-conversation-list';
 export * from './use-post-get-conversation-with';
 export * from './use-post-send-message';
+export * from './use-post-messages-received';
+export * from './use-post-messages-read';
+export * from './use-post-react-to-message';
+export * from './use-post-delete-message';

+ 17 - 0
src/modules/api/chat/queries/use-post-delete-message.tsx

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

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

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

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

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

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

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

+ 142 - 8
src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx

@@ -26,7 +26,6 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
 import EmojiSelector from 'react-native-emoji-selector';
 import * as ImagePicker from 'expo-image-picker';
 import { useActionSheet } from '@expo/react-native-action-sheet';
-import { ActionSheetProvider } from '@expo/react-native-action-sheet';
 import {
   GestureHandlerRootView,
   LongPressGestureHandler,
@@ -52,9 +51,15 @@ import Clipboard from '@react-native-clipboard/clipboard';
 import { trigger } from 'react-native-haptic-feedback';
 import ReactModal from 'react-native-modal';
 import { storage, StoreType } from 'src/storage';
-import { usePostGetChatWithQuery, usePostSendMessageMutation } from '@api/chat';
+import {
+  usePostDeleteMessageMutation,
+  usePostGetChatWithQuery,
+  usePostMessagesReadMutation,
+  usePostSendMessageMutation
+} from '@api/chat';
 import { Message } from '../types';
 import { API_HOST } from 'src/constants';
+import { getFontSize } from 'src/utils';
 
 const options = {
   enableVibrateFallback: true,
@@ -92,8 +97,12 @@ const ChatScreen = ({ route }: { route: any }) => {
   } | null>(null);
 
   const [isModalVisible, setIsModalVisible] = useState(false);
+  const [unreadMessageIndex, setUnreadMessageIndex] = useState<number | null>(null);
+  const { mutateAsync: markMessagesAsRead } = usePostMessagesReadMutation();
+  const { mutateAsync: deleteMessage } = usePostDeleteMessageMutation();
 
   const messageRefs = useRef<{ [key: string]: any }>({});
+  const flatList = useRef<FlatList | null>(null);
   const scrollY = useSharedValue(0);
 
   const mapApiMessageToGiftedMessage = (message: Message): IMessage => {
@@ -110,7 +119,8 @@ const ChatScreen = ({ route }: { route: any }) => {
       attachment: message.attachement !== -1 ? message.attachement : null,
       pending: message.status === 1,
       sent: message.status === 2,
-      received: message.status === 3
+      received: message.status === 3,
+      deleted: message.status === 4
     };
   };
 
@@ -118,11 +128,67 @@ const ChatScreen = ({ route }: { route: any }) => {
     useCallback(() => {
       if (chatData?.messages) {
         const mappedMessages = chatData.messages.map(mapApiMessageToGiftedMessage);
+
+        const firstUnreadIndex = mappedMessages.findLastIndex(
+          (msg) => !msg.received && !msg?.deleted && msg.user._id === id
+        );
+        if (firstUnreadIndex !== -1) {
+          setUnreadMessageIndex(firstUnreadIndex);
+
+          const unreadMarker: any = {
+            _id: 'unreadMarker',
+            text: 'Unread messages',
+            system: true
+          };
+
+          mappedMessages.splice(firstUnreadIndex, 0, unreadMarker);
+        }
         setMessages(mappedMessages);
       }
     }, [chatData])
   );
 
+  const sentToServer = useRef<Set<number>>(new Set());
+
+  const handleViewableItemsChanged = ({ viewableItems }: { viewableItems: any[] }) => {
+    const newViewableUnreadMessages = viewableItems
+      .filter(
+        (item) =>
+          !item.item.received &&
+          !item.item.deleted &&
+          !item.item.system &&
+          item.item.user._id === id &&
+          !sentToServer.current.has(item.item._id)
+      )
+      .map((item) => item.item._id);
+
+    if (newViewableUnreadMessages.length > 0) {
+      markMessagesAsRead(
+        {
+          token,
+          from_user: id,
+          messages_id: newViewableUnreadMessages
+        },
+        {
+          onSuccess: (res) => {
+            newViewableUnreadMessages.forEach((id) => sentToServer.current.add(id));
+          }
+        }
+      );
+    }
+  };
+
+  const renderSystemMessage = (props: any) => {
+    if (props.currentMessage._id === 'unreadMarker') {
+      return (
+        <View style={styles.unreadMessagesContainer}>
+          <Text style={styles.unreadMessagesText}>{props.currentMessage.text}</Text>
+        </View>
+      );
+    }
+    return null;
+  };
+
   const clearReplyMessage = () => setReplyMessage(null);
 
   const handleLongPress = (message: IMessage, props) => {
@@ -191,6 +257,20 @@ const ChatScreen = ({ route }: { route: any }) => {
     }
   };
 
+  const handleDeleteMessage = (messageId: number) => {
+    deleteMessage(
+      {
+        message_id: messageId,
+        conversation_with_user: id
+      },
+      {
+        onSuccess: (res) => {
+          setMessages((prevMessages) => prevMessages.filter((msg) => msg._id !== messageId));
+        }
+      }
+    );
+  };
+
   const handleOptionPress = (option: string) => {
     switch (option) {
       case 'reply':
@@ -201,10 +281,8 @@ const ChatScreen = ({ route }: { route: any }) => {
         Alert.alert('copied');
         break;
       case 'delete':
-        setMessages((prevMessages) =>
-          prevMessages.filter((msg) => msg._id !== selectedMessage?.currentMessage?._id)
-        );
-        Alert.alert('deleted');
+        handleDeleteMessage(selectedMessage?.currentMessage?._id);
+        setIsModalVisible(false);
         break;
       case 'pin':
         Alert.alert(option);
@@ -534,6 +612,41 @@ const ChatScreen = ({ route }: { route: any }) => {
   const renderBubble = (props: any) => {
     const { currentMessage } = props;
 
+    if (currentMessage.deleted) {
+      return (
+        <View>
+          <Bubble
+            {...props}
+            renderTime={() => null}
+            currentMessage={{ ...props.currentMessage, text: 'This message was deleted' }}
+            renderMessageText={() => (
+              <View style={{ paddingHorizontal: 12, paddingVertical: 6 }}>
+                <Text style={{ color: Colors.LIGHT_GRAY, fontStyle: 'italic', fontSize: 12 }}>
+                  This message was deleted
+                </Text>
+              </View>
+            )}
+            wrapperStyle={{
+              right: {
+                backgroundColor: Colors.DARK_BLUE
+              },
+              left: {
+                backgroundColor: Colors.FILL_LIGHT
+              }
+            }}
+            textStyle={{
+              left: {
+                color: Colors.DARK_BLUE
+              },
+              right: {
+                color: Colors.FILL_LIGHT
+              }
+            }}
+          />
+        </View>
+      );
+    }
+
     return (
       <View
         ref={(ref) => {
@@ -648,9 +761,20 @@ const ChatScreen = ({ route }: { route: any }) => {
         <GiftedChat
           messages={messages}
           listViewProps={{
+            ref: flatList,
             showsVerticalScrollIndicator: false,
-            initialNumToRender: 20
+            initialNumToRender: 30,
+            onViewableItemsChanged: handleViewableItemsChanged,
+            viewabilityConfig: { itemVisiblePercentThreshold: 50 },
+            initialScrollIndex: unreadMessageIndex ?? 0,
+            onScrollToIndexFailed: (info: any) => {
+              const wait = new Promise((resolve) => setTimeout(resolve, 300));
+              wait.then(() => {
+                flatList.current?.scrollToIndex({ index: info.index, animated: true });
+              });
+            }
           }}
+          renderSystemMessage={renderSystemMessage}
           onSend={(newMessages: IMessage[]) => onSend(newMessages)}
           user={{ _id: +currentUserId, name: 'Me' }}
           renderBubble={renderBubble}
@@ -769,6 +893,16 @@ const ChatScreen = ({ route }: { route: any }) => {
 };
 
 const styles = StyleSheet.create({
+  unreadMessagesContainer: {
+    padding: 8,
+    backgroundColor: Colors.FILL_LIGHT,
+    alignItems: 'center'
+  },
+  unreadMessagesText: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '700',
+    fontSize: getFontSize(12)
+  },
   emojiSelectorContainer: {
     position: 'absolute',
     bottom: 0,

+ 6 - 2
src/screens/InAppScreens/MessagesScreen/Components/ChatMessageBox.tsx

@@ -52,11 +52,15 @@ const ChatMessageBox = ({ setReplyOnSwipeOpen, updateRowRef, ...props }: ChatMes
 
   const onSwipeOpenAction = () => {
     if (props.currentMessage) {
-      setReplyOnSwipeOpen({ ...props.currentMessage });
       trigger('impactMedium', options);
+      setReplyOnSwipeOpen({ ...props.currentMessage });
     }
   };
 
+  if (props.currentMessage?.deleted || props.currentMessage?.system) {
+    return <Message {...props} />;
+  }
+
   return (
     <GestureHandlerRootView>
       <Swipeable
@@ -74,7 +78,7 @@ const ChatMessageBox = ({ setReplyOnSwipeOpen, updateRowRef, ...props }: ChatMes
 
 const styles = StyleSheet.create({
   container: {
-    width: 40,
+    width: 40
   },
   replyImageWrapper: {
     flex: 1,

+ 1 - 1
src/screens/InAppScreens/MessagesScreen/types.ts

@@ -27,7 +27,7 @@ export type MessageSimple = {
   sender: number;
   recipient: number;
   text: string;
-  status: 1 | 2 | 3;
+  status: 1 | 2 | 3 | 4;
   sent_datetime: Date;
   received_datetime: Date | null;
   read_datetime: Date | null;

+ 10 - 2
src/types/api.ts

@@ -128,7 +128,11 @@ export enum API_ENDPOINT {
   SEARCH_USERS = 'search-users',
   GET_CHATS_LIST = 'get-conversation-list',
   GET_CHAT_WITH = 'get-conversation-with',
-  SEND_MESSAGE = 'send-message'
+  SEND_MESSAGE = 'send-message',
+  MESSAGES_RECEIVED = 'messages-received',
+  MESSAGES_READ = 'messages-read',
+  REACT_TO_MESSAGE = 'react-to-message',
+  DELETE_MESSAGE = 'delete-message'
 }
 
 export enum API {
@@ -233,7 +237,11 @@ export enum API {
   SEARCH_USERS = `${API_ROUTE.CHAT}/${API_ENDPOINT.SEARCH_USERS}`,
   GET_CHATS_LIST = `${API_ROUTE.CHAT}/${API_ENDPOINT.GET_CHATS_LIST}`,
   GET_CHAT_WITH = `${API_ROUTE.CHAT}/${API_ENDPOINT.GET_CHAT_WITH}`,
-  SEND_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.SEND_MESSAGE}`
+  SEND_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.SEND_MESSAGE}`,
+  MESSAGES_RECEIVED = `${API_ROUTE.CHAT}/${API_ENDPOINT.MESSAGES_RECEIVED}`,
+  MESSAGES_READ = `${API_ROUTE.CHAT}/${API_ENDPOINT.MESSAGES_READ}`,
+  REACT_TO_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.REACT_TO_MESSAGE}`,
+  DELETE_MESSAGE = `${API_ROUTE.CHAT}/${API_ENDPOINT.DELETE_MESSAGE}`
 }
 
 export type BaseAxiosError = AxiosError;