Pārlūkot izejas kodu

reactions/archive/reply func/haptic

Viktoriia 8 mēneši atpakaļ
vecāks
revīzija
7159fb7e0a

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

@@ -57,6 +57,7 @@ export interface PostGetChatWithReturn extends ResponseType {
 export interface PostSendMessage {
   to_uid: number;
   text: string;
+  reply_to_id?: number;
 }
 
 export interface PostMessagesReceivedOrRead {

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

@@ -6,3 +6,7 @@ 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';
+export * from './use-post-set-pin';
+export * from './use-post-set-archive';
+export * from './use-post-set-mute';
+export * from './use-post-set-block';

+ 233 - 241
src/screens/InAppScreens/MessagesScreen/ChatScreen/index.tsx

@@ -19,11 +19,9 @@ import {
   IMessage,
   Send,
   BubbleProps,
-  QuickRepliesProps,
   Composer
 } from 'react-native-gifted-chat';
 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 {
@@ -37,14 +35,7 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native';
 import { Video } from 'expo-av';
 import ChatMessageBox from '../Components/ChatMessageBox';
 import ReplyMessageBar from '../Components/ReplyMessageBar';
-import Animated, {
-  FadeIn,
-  FadeOut,
-  SlideInDown,
-  SlideOutDown,
-  useSharedValue,
-  withTiming
-} from 'react-native-reanimated';
+import { useSharedValue, withTiming } from 'react-native-reanimated';
 import { BlurView } from 'expo-blur';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 import Clipboard from '@react-native-clipboard/clipboard';
@@ -55,11 +46,15 @@ import {
   usePostDeleteMessageMutation,
   usePostGetChatWithQuery,
   usePostMessagesReadMutation,
+  usePostReactToMessageMutation,
   usePostSendMessageMutation
 } from '@api/chat';
 import { Message } from '../types';
 import { API_HOST } from 'src/constants';
 import { getFontSize } from 'src/utils';
+import ReactionBar from '../Components/ReactionBar';
+import OptionsMenu from '../Components/OptionsMenu';
+import EmojiSelectorModal from '../Components/EmojiSelectorModal';
 
 const options = {
   enableVibrateFallback: true,
@@ -86,7 +81,6 @@ const ChatScreen = ({ route }: { route: any }) => {
   const [replyMessage, setReplyMessage] = useState<IMessage | null>(null);
 
   const [selectedMessage, setSelectedMessage] = useState<IMessage | null>(null);
-  const [showReactions, setShowReactions] = useState<number | null>(null);
   const [emojiSelectorVisible, setEmojiSelectorVisible] = useState(false);
   const [messagePosition, setMessagePosition] = useState<{
     x: number;
@@ -100,6 +94,9 @@ const ChatScreen = ({ route }: { route: any }) => {
   const [unreadMessageIndex, setUnreadMessageIndex] = useState<number | null>(null);
   const { mutateAsync: markMessagesAsRead } = usePostMessagesReadMutation();
   const { mutateAsync: deleteMessage } = usePostDeleteMessageMutation();
+  const { mutateAsync: reactToMessage } = usePostReactToMessageMutation();
+
+  const [highlightedMessageId, setHighlightedMessageId] = useState<number | null>(null);
 
   const messageRefs = useRef<{ [key: string]: any }>({});
   const flatList = useRef<FlatList | null>(null);
@@ -114,7 +111,13 @@ const ChatScreen = ({ route }: { route: any }) => {
         _id: message.sender,
         name: message.sender === id ? name : 'Me'
       },
-      replyMessage: message.reply_to_id !== -1 ? { text: message.reply_to.text } : null,
+      replyMessage:
+        message.reply_to_id !== -1
+          ? {
+              text: message.reply_to.text,
+              id: message.reply_to.id
+            }
+          : null,
       reactions: JSON.parse(message.reactions || '{}'),
       attachment: message.attachement !== -1 ? message.attachement : null,
       pending: message.status === 1,
@@ -253,7 +256,7 @@ const ChatScreen = ({ route }: { route: any }) => {
     if (emoji === '+') {
       openEmojiSelector();
     } else {
-      // addReaction(messageId, emoji);
+      addReaction(messageId, emoji);
     }
   };
 
@@ -274,10 +277,12 @@ const ChatScreen = ({ route }: { route: any }) => {
   const handleOptionPress = (option: string) => {
     switch (option) {
       case 'reply':
-        Alert.alert(option);
+        setReplyMessage(selectedMessage?.currentMessage);
+        setIsModalVisible(false);
         break;
       case 'copy':
         Clipboard.setString(selectedMessage?.currentMessage?.text ?? '');
+        setIsModalVisible(false);
         Alert.alert('copied');
         break;
       case 'delete':
@@ -285,6 +290,7 @@ const ChatScreen = ({ route }: { route: any }) => {
         setIsModalVisible(false);
         break;
       case 'pin':
+        setIsModalVisible(false);
         Alert.alert(option);
         break;
       default:
@@ -293,83 +299,6 @@ const ChatScreen = ({ route }: { route: any }) => {
     closeEmojiSelector();
   };
 
-  const renderReactionsBar = () =>
-    selectedMessage &&
-    messagePosition && (
-      <Animated.View
-        entering={FadeIn}
-        exiting={FadeOut}
-        style={[
-          styles.reactionBar,
-          {
-            top: messagePosition.y - 50, // - reaction bar height
-            left: messagePosition.isMine
-              ? Dimensions.get('window').width - Dimensions.get('window').width * 0.75 - 8 // reaction bar width
-              : messagePosition.x // + padding
-          }
-        ]}
-      >
-        {reactionEmojis.map((emoji) => (
-          <TouchableOpacity
-            key={emoji}
-            onPress={() => handleReactionPress(emoji, selectedMessage?.currentMessage?._id)}
-          >
-            <Text style={styles.reactionEmoji}>{emoji}</Text>
-          </TouchableOpacity>
-        ))}
-        <TouchableOpacity
-          onPress={() => openEmojiSelector()}
-          style={{
-            alignItems: 'center',
-            justifyContent: 'center',
-            backgroundColor: Colors.FILL_LIGHT,
-            borderRadius: 15,
-            borderWidth: 1,
-            borderColor: Colors.BORDER_LIGHT,
-            width: 30,
-            height: 30
-          }}
-        >
-          <MaterialCommunityIcons name="plus" size={28} color={Colors.DARK_BLUE} />
-        </TouchableOpacity>
-      </Animated.View>
-    );
-
-  const renderOptionsMenu = () =>
-    selectedMessage &&
-    messagePosition && (
-      <Animated.View
-        entering={FadeIn}
-        exiting={FadeOut}
-        style={[
-          styles.optionsMenu,
-          {
-            top: messagePosition.y + messagePosition.height + 10,
-            left: messagePosition.isMine
-              ? Dimensions.get('window').width - Dimensions.get('window').width * 0.75 - 8
-              : messagePosition.x
-          }
-        ]}
-      >
-        <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('reply')}>
-          <Text style={styles.optionText}>Reply</Text>
-          <MaterialCommunityIcons name="reply" size={20} color={Colors.DARK_BLUE} />
-        </TouchableOpacity>
-        <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('copy')}>
-          <Text style={styles.optionText}>Copy</Text>
-          <MaterialCommunityIcons name="content-copy" size={20} color={Colors.DARK_BLUE} />
-        </TouchableOpacity>
-        <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('delete')}>
-          <Text style={styles.optionText}>Delete</Text>
-          <MaterialCommunityIcons name="delete" size={20} color={Colors.DARK_BLUE} />
-        </TouchableOpacity>
-        <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('pin')}>
-          <Text style={styles.optionText}>Pin message</Text>
-          <MaterialCommunityIcons name="pin" size={20} color={Colors.DARK_BLUE} />
-        </TouchableOpacity>
-      </Animated.View>
-    );
-
   const renderSelectedMessage = () =>
     selectedMessage && (
       <View
@@ -407,6 +336,7 @@ const ChatScreen = ({ route }: { route: any }) => {
                 </View>
               ) : null;
             }}
+            renderCustomView={renderReplyMessageView}
           />
         </ScrollView>
       </View>
@@ -438,13 +368,18 @@ const ChatScreen = ({ route }: { route: any }) => {
     (newMessages: IMessage[] = []) => {
       if (replyMessage) {
         newMessages[0].replyMessage = {
-          text: replyMessage.text
+          text: replyMessage.text,
+          id: replyMessage._id
         };
       }
       const message = { ...newMessages[0], pending: true };
 
       sendMessage(
-        { to_uid: id, text: message.text },
+        {
+          to_uid: id,
+          text: message.text,
+          reply_to_id: replyMessage ? (replyMessage._id as number) : -1
+        },
         {
           onSuccess: (res) => console.log('res', res),
           onError: (err) => console.log('err', err)
@@ -554,18 +489,33 @@ const ChatScreen = ({ route }: { route: any }) => {
     return null;
   };
 
-  const addReaction = (messageId: number, reaction: any) => {
-    const updatedMessages = messages.map((msg: any) => {
+  const addReaction = (messageId: number, reaction: string) => {
+    const updatedMessages = messages.map((msg) => {
       if (msg._id === messageId) {
+        const updatedReactions = [
+          ...(Array.isArray(msg.reactions) ? msg.reactions : []),
+          { datetime: new Date().toISOString(), reaction: reaction, uid: +currentUserId }
+        ];
+
         return {
           ...msg,
-          reactions: [...(msg.reactions ?? []), reaction]
+          reactions: updatedReactions
         };
       }
       return msg;
     });
+
     setMessages(updatedMessages);
-    setShowReactions(null);
+
+    reactToMessage(
+      { message_id: messageId, reaction: reaction, conversation_with_user: id },
+      {
+        onSuccess: (res) => console.log('res', res),
+        onError: (err) => console.log('err', err)
+      }
+    );
+
+    setIsModalVisible(false);
   };
 
   const updateRowRef = useCallback(
@@ -581,14 +531,88 @@ const ChatScreen = ({ route }: { route: any }) => {
     [replyMessage]
   );
 
-  const renderReplyMessageView = (props: BubbleProps<IMessage>) =>
-    props.currentMessage &&
-    props.currentMessage?.replyMessage && (
-      <View style={styles.replyMessageContainer}>
-        <Text>{props.currentMessage.replyMessage.text}</Text>
-        <View style={styles.replyMessageDivider} />
+  const renderCustomView = (props: BubbleProps<IMessage>) => {
+    if (!props.currentMessage) {
+      return null;
+    }
+    const { currentMessage } = props;
+
+    if (!currentMessage) {
+      return null;
+    }
+
+    return (
+      <View style={{ position: 'relative' }}>
+        {renderReplyMessageView(props)}
+
+        {currentMessage.reactions &&
+          Array.isArray(currentMessage.reactions) &&
+          currentMessage.reactions.length > 0 && (
+            <View
+              style={[
+                styles.reactionsContainer,
+                currentMessage.user._id === id ? { right: -15 } : { left: -15 }
+              ]}
+            >
+              {Object.entries(
+                currentMessage.reactions.reduce((acc, { reaction }) => {
+                  acc[reaction] = (acc[reaction] || 0) + 1;
+                  return acc;
+                }, {})
+              ).map(([emoji, count]) => (
+                <Text key={emoji} style={{}}>
+                  {emoji}
+                  {(count as number) > 1 ? ` ${count}` : ''}
+                </Text>
+              ))}
+            </View>
+          )}
       </View>
     );
+  };
+
+  const renderReplyMessageView = (props: BubbleProps<IMessage>) => {
+    if (!props.currentMessage) {
+      return null;
+    }
+    const { currentMessage } = props;
+
+    if (!currentMessage || !currentMessage?.replyMessage) {
+      return null;
+    }
+
+    return (
+      <TouchableOpacity
+        style={styles.replyMessageContainer}
+        onPress={() => {
+          if (currentMessage?.replyMessage?.id) {
+            scrollToMessage(currentMessage.replyMessage.id);
+          }
+        }}
+      >
+        <Text>{currentMessage.replyMessage.text}</Text>
+        <View style={styles.replyMessageDivider} />
+      </TouchableOpacity>
+    );
+  };
+
+  const scrollToMessage = (messageId: number) => {
+    const messageIndex = messages.findIndex((message) => message._id === messageId);
+
+    if (messageIndex !== -1 && flatList.current) {
+      flatList.current.scrollToIndex({
+        index: messageIndex,
+        animated: true,
+        viewPosition: 0.5
+      });
+
+      setHighlightedMessageId(messageId);
+
+      setTimeout(() => {
+        setHighlightedMessageId(null);
+      }, 1000);
+    }
+  };
 
   useEffect(() => {
     if (replyMessage && swipeableRowRef.current) {
@@ -647,57 +671,77 @@ const ChatScreen = ({ route }: { route: any }) => {
       );
     }
 
+    const isHighlighted = currentMessage._id === highlightedMessageId;
+    const backgroundColor = isHighlighted
+      ? Colors.ORANGE
+      : currentMessage.user._id === +currentUserId
+        ? Colors.DARK_BLUE
+        : Colors.FILL_LIGHT;
+
     return (
       <View
-        ref={(ref) => {
-          if (ref && currentMessage) {
-            messageRefs.current[currentMessage._id] = ref;
-          }
-        }}
-        collapsable={false}
+        style={[
+          currentMessage.reactions &&
+          Array.isArray(currentMessage.reactions) &&
+          currentMessage.reactions.length > 0
+            ? { paddingBottom: 15 }
+            : {}
+        ]}
       >
-        <Bubble
-          {...props}
-          wrapperStyle={{
-            right: {
-              backgroundColor: Colors.DARK_BLUE
-            },
-            left: {
-              backgroundColor: Colors.FILL_LIGHT
-            }
-          }}
-          textStyle={{
-            left: {
-              color: Colors.DARK_BLUE
-            },
-            right: {
-              color: Colors.FILL_LIGHT
+        <View
+          ref={(ref) => {
+            if (ref && currentMessage) {
+              messageRefs.current[currentMessage._id] = ref;
             }
           }}
-          onLongPress={() => handleLongPress(currentMessage, props)}
-          renderTicks={(message: IMessage) => {
-            return message.user._id === +currentUserId && message.received ? (
-              <View style={{ paddingRight: 8, bottom: 2 }}>
-                <MaterialCommunityIcons name="check-all" size={16} color={Colors.WHITE} />
-              </View>
-            ) : message.user._id === +currentUserId && message.sent ? (
-              <View style={{ paddingRight: 8, bottom: 2 }}>
-                <MaterialCommunityIcons name="check" size={16} color={Colors.WHITE} />
-              </View>
-            ) : message.user._id === +currentUserId && message.pending ? (
-              <View style={{ paddingRight: 8, bottom: 2 }}>
-                <MaterialCommunityIcons name="check" size={16} color={Colors.LIGHT_GRAY} />
-              </View>
-            ) : null;
-          }}
-          // renderQuickReplies={(quickReplies: QuickRepliesProps<IMessage>) => null}
-          // renderQuickReplies={(quickReplies: QuickRepliesProps<IMessage>) => (
-          //   <View style={{height: 20, width: 20, backgroundColor: 'green', bottom: 0, right: 0}}>
-          //     <Text>{currentMessage.quickReplies.values[0].title}</Text>
-          //   </View>
-
-          // )}
-        />
+          collapsable={false}
+        >
+          <Bubble
+            {...props}
+            wrapperStyle={{
+              right: {
+                backgroundColor: backgroundColor
+              },
+              left: {
+                backgroundColor: backgroundColor
+              }
+            }}
+            textStyle={{
+              left: {
+                color: Colors.DARK_BLUE
+              },
+              right: {
+                color: Colors.FILL_LIGHT
+              }
+            }}
+            onLongPress={() => handleLongPress(currentMessage, props)}
+            renderTicks={(message: IMessage) => {
+              return message.user._id === +currentUserId && message.received ? (
+                <View style={{ paddingRight: 8, bottom: 2 }}>
+                  <MaterialCommunityIcons name="check-all" size={16} color={Colors.WHITE} />
+                </View>
+              ) : message.user._id === +currentUserId && message.sent ? (
+                <View style={{ paddingRight: 8, bottom: 2 }}>
+                  <MaterialCommunityIcons name="check" size={16} color={Colors.WHITE} />
+                </View>
+              ) : message.user._id === +currentUserId && message.pending ? (
+                <View style={{ paddingRight: 8, bottom: 2 }}>
+                  <MaterialCommunityIcons name="check" size={16} color={Colors.LIGHT_GRAY} />
+                </View>
+              ) : null;
+            }}
+            renderCustomView={renderCustomView}
+            isCustomViewBottom={true} // should be false to show reply on top
+            // bottomContainerStyle={{
+            //   right: {
+            //     backgroundColor: 'red'
+            //   },
+            //   left: {
+            //     backgroundColor: 'red'
+            //   }
+            // }}
+          />
+        </View>
       </View>
     );
   };
@@ -714,28 +758,6 @@ const ChatScreen = ({ route }: { route: any }) => {
     />
   );
 
-  const renderEmojiSelector = () => (
-    <Animated.View
-      entering={SlideInDown}
-      exiting={SlideOutDown}
-      style={styles.emojiSelectorContainer}
-    >
-      <EmojiSelector
-        onEmojiSelected={(emoji) => {
-          addReaction(selectedMessage?._id, emoji);
-          closeEmojiSelector();
-        }}
-        showSearchBar={true}
-        columns={8}
-      />
-      <TouchableOpacity style={styles.closeModalButton} onPress={closeEmojiSelector}>
-        <MaterialCommunityIcons name="close" size={30} color={Colors.DARK_BLUE} />
-      </TouchableOpacity>
-    </Animated.View>
-  );
-
-  if (!messages.length) return null;
-
   return (
     <PageWrapper style={{ marginLeft: 0, marginRight: 0 }}>
       <View style={{ paddingHorizontal: '5%' }}>
@@ -769,7 +791,11 @@ const ChatScreen = ({ route }: { route: any }) => {
             onScrollToIndexFailed: (info: any) => {
               const wait = new Promise((resolve) => setTimeout(resolve, 300));
               wait.then(() => {
-                flatList.current?.scrollToIndex({ index: info.index, animated: true });
+                flatList.current?.scrollToIndex({
+                  index: info.index,
+                  animated: true,
+                  viewPosition: 0.5
+                });
               });
             }
           }}
@@ -831,13 +857,15 @@ const ChatScreen = ({ route }: { route: any }) => {
           renderChatFooter={() => (
             <ReplyMessageBar clearReply={clearReplyMessage} message={replyMessage} />
           )}
-          renderCustomView={renderReplyMessageView}
           renderMessageVideo={renderMessageVideo}
           renderAvatar={null}
           maxComposerHeight={100}
           renderComposer={(props) => <Composer {...props} />}
           isCustomViewBottom={true}
           keyboardShouldPersistTaps="handled"
+          shouldUpdateMessage={(props, nextProps) =>
+            highlightedMessageId === nextProps.currentMessage._id
+          }
           // inverted={true}
           // isTyping={true}
         />
@@ -848,7 +876,6 @@ const ChatScreen = ({ route }: { route: any }) => {
               <Video
                 source={{ uri: selectedMedia }}
                 style={styles.fullScreenMedia}
-                // resizeMode="cover"
                 useNativeControls
               />
             ) : (
@@ -879,10 +906,25 @@ const ChatScreen = ({ route }: { route: any }) => {
               activeOpacity={1}
               onPress={handleBackgroundPress}
             >
-              {renderReactionsBar()}
+              <ReactionBar
+                messagePosition={messagePosition}
+                selectedMessage={selectedMessage}
+                reactionEmojis={reactionEmojis}
+                handleReactionPress={handleReactionPress}
+                openEmojiSelector={openEmojiSelector}
+              />
               {renderSelectedMessage()}
-              {renderOptionsMenu()}
-              {emojiSelectorVisible ? renderEmojiSelector() : null}
+              <OptionsMenu
+                selectedMessage={selectedMessage}
+                handleOptionPress={handleOptionPress}
+                messagePosition={messagePosition}
+              />
+              <EmojiSelectorModal
+                visible={emojiSelectorVisible}
+                selectedMessage={selectedMessage}
+                addReaction={addReaction}
+                closeEmojiSelector={closeEmojiSelector}
+              />
             </TouchableOpacity>
           </BlurView>
         </ReactModal>
@@ -902,73 +944,12 @@ const styles = StyleSheet.create({
     fontWeight: '700',
     fontSize: getFontSize(12)
   },
-  emojiSelectorContainer: {
-    position: 'absolute',
-    bottom: 0,
-    width: '100%',
-    height: '50%',
-    backgroundColor: 'white',
-    borderTopLeftRadius: 15,
-    borderTopRightRadius: 15,
-    shadowColor: '#000',
-    shadowOpacity: 0.3,
-    shadowOffset: { width: 0, height: -2 },
-    shadowRadius: 5,
-    elevation: 5,
-    padding: 10
-  },
-
   modalBackground: {
     flex: 1
   },
   modalContent: {
     backgroundColor: 'transparent'
   },
-  reactionBar: {
-    position: 'absolute',
-    width: Dimensions.get('window').width * 0.75,
-    flexDirection: 'row',
-    backgroundColor: 'rgba(255, 255, 255, 0.9)',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    borderRadius: 20,
-    padding: 5,
-    paddingHorizontal: 12,
-    shadowColor: '#000',
-    shadowOpacity: 0.3,
-    shadowOffset: { width: 0, height: 2 },
-    shadowRadius: 5,
-    elevation: 5
-  },
-  reactionEmoji: {
-    fontSize: 28
-  },
-  closeModalButton: {
-    position: 'absolute',
-    top: 10,
-    right: 10
-  },
-  optionsMenu: {
-    position: 'absolute',
-    backgroundColor: 'rgba(255, 255, 255, 0.9)',
-    borderRadius: 10,
-    padding: 8,
-    shadowColor: '#000',
-    shadowOpacity: 0.3,
-    shadowOffset: { width: 0, height: 2 },
-    shadowRadius: 5,
-    elevation: 5,
-    width: Dimensions.get('window').width * 0.75
-  },
-  optionButton: {
-    paddingVertical: 10,
-    paddingHorizontal: 12,
-    flexDirection: 'row',
-    justifyContent: 'space-between'
-  },
-  optionText: {
-    fontSize: 16
-  },
   mediaContainer: {
     borderRadius: 10,
     overflow: 'hidden',
@@ -1045,10 +1026,21 @@ const styles = StyleSheet.create({
     fontStyle: 'italic'
   },
   reactionsContainer: {
+    height: 25,
+    paddingHorizontal: 6,
+    backgroundColor: Colors.FILL_LIGHT,
+    bottom: -30,
+    position: 'absolute',
+    alignItems: 'center',
+    justifyContent: 'center',
     flexDirection: 'row',
-    justifyContent: 'flex-start',
-    paddingHorizontal: 5,
-    marginTop: -10
+    gap: 6,
+    borderRadius: 15,
+    shadowColor: Colors.DARK_BLUE,
+    shadowOpacity: 0.2,
+    shadowOffset: { width: 0, height: 1 },
+    shadowRadius: 2,
+    elevation: 1
   },
   reactionText: {
     fontSize: 16,

+ 1 - 1
src/screens/InAppScreens/MessagesScreen/Components/ChatMessageBox.tsx

@@ -52,7 +52,6 @@ const ChatMessageBox = ({ setReplyOnSwipeOpen, updateRowRef, ...props }: ChatMes
 
   const onSwipeOpenAction = () => {
     if (props.currentMessage) {
-      trigger('impactMedium', options);
       setReplyOnSwipeOpen({ ...props.currentMessage });
     }
   };
@@ -69,6 +68,7 @@ const ChatMessageBox = ({ setReplyOnSwipeOpen, updateRowRef, ...props }: ChatMes
         rightThreshold={40}
         renderRightActions={renderRightAction}
         onSwipeableOpen={onSwipeOpenAction}
+        onSwipeableWillOpen={() => trigger('impactMedium', options)}
       >
         <Message {...props} />
       </Swipeable>

+ 64 - 0
src/screens/InAppScreens/MessagesScreen/Components/EmojiSelectorModal.tsx

@@ -0,0 +1,64 @@
+import React from 'react';
+import { TouchableOpacity, StyleSheet } from 'react-native';
+import EmojiSelector from 'react-native-emoji-selector';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import Animated, { SlideInDown, SlideOutDown } from 'react-native-reanimated';
+import { Colors } from 'src/theme';
+
+interface EmojiSelectorModalProps {
+  visible: boolean;
+  addReaction: (messageId: number, emoji: string) => void;
+  selectedMessage: any;
+  closeEmojiSelector: () => void;
+}
+
+const EmojiSelectorModal: React.FC<EmojiSelectorModalProps> = ({
+  visible,
+  addReaction,
+  selectedMessage,
+  closeEmojiSelector
+}) =>
+  visible && (
+    <Animated.View
+      entering={SlideInDown}
+      exiting={SlideOutDown}
+      style={styles.emojiSelectorContainer}
+    >
+      <EmojiSelector
+        onEmojiSelected={(emoji) => {
+          addReaction(selectedMessage?.currentMessage?._id, emoji);
+          closeEmojiSelector();
+        }}
+        showSearchBar={true}
+        columns={8}
+      />
+      <TouchableOpacity style={styles.closeModalButton} onPress={closeEmojiSelector}>
+        <MaterialCommunityIcons name="close" size={30} color={Colors.DARK_BLUE} />
+      </TouchableOpacity>
+    </Animated.View>
+  );
+
+const styles = StyleSheet.create({
+  emojiSelectorContainer: {
+    position: 'absolute',
+    bottom: 0,
+    width: '100%',
+    height: '50%',
+    backgroundColor: 'white',
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    shadowColor: '#000',
+    shadowOpacity: 0.3,
+    shadowOffset: { width: 0, height: -2 },
+    shadowRadius: 5,
+    elevation: 5,
+    padding: 10
+  },
+  closeModalButton: {
+    position: 'absolute',
+    top: 10,
+    right: 10
+  }
+});
+
+export default EmojiSelectorModal;

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

@@ -9,17 +9,34 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
 import { ChatProps } from '../types';
 import { useNavigation } from '@react-navigation/native';
 import { NAVIGATION_PAGES } from 'src/types';
+import { usePostSetBlockMutation, usePostSetMuteMutation } from '@api/chat';
 
 const MoreModal = () => {
   const insets = useSafeAreaInsets();
   const navigation = useNavigation();
 
   const [chatData, setChatData] = useState<ChatProps | null>(null);
+  const { mutateAsync: muteUser } = usePostSetMuteMutation();
+  const { mutateAsync: blockUser } = usePostSetBlockMutation();
 
   const handleSheetOpen = (payload: ChatProps | null) => {
     setChatData(payload);
   };
 
+  const handleMute = async () => {
+    // await muteUser({
+    //   value: chatData.muted === 1 ? 0 : 1,
+    //   conversation_with_user: chatData.id
+    // });
+  };
+
+  const handleBlock = async () => {
+    // await blockUser({
+    //   value: chatData.blocked === 1 ? 0 : 1,
+    //   conversation_with_user: chatData.id
+    // });
+  };
+
   return (
     <ActionSheet
       id="more-modal"

+ 84 - 0
src/screens/InAppScreens/MessagesScreen/Components/OptionsMenu.tsx

@@ -0,0 +1,84 @@
+import React from 'react';
+import { TouchableOpacity, Text, StyleSheet, Dimensions } from 'react-native';
+import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { Colors } from 'src/theme';
+
+interface MessagePosition {
+  x: number;
+  y: number;
+  isMine: boolean;
+  width: number;
+  height: number;
+}
+
+interface OptionsMenuProps {
+  messagePosition: MessagePosition | null;
+  selectedMessage: any;
+  handleOptionPress: (option: string) => void;
+}
+
+const OptionsMenu: React.FC<OptionsMenuProps> = ({
+  messagePosition,
+  selectedMessage,
+  handleOptionPress
+}) =>
+  selectedMessage &&
+  messagePosition && (
+    <Animated.View
+      entering={FadeIn}
+      exiting={FadeOut}
+      style={[
+        styles.optionsMenu,
+        {
+          top: messagePosition.y + messagePosition.height + 10,
+          left: messagePosition.isMine
+            ? Dimensions.get('window').width - Dimensions.get('window').width * 0.75 - 8
+            : messagePosition.x
+        }
+      ]}
+    >
+      <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('reply')}>
+        <Text style={styles.optionText}>Reply</Text>
+        <MaterialCommunityIcons name="reply" size={20} color={Colors.DARK_BLUE} />
+      </TouchableOpacity>
+      <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('copy')}>
+        <Text style={styles.optionText}>Copy</Text>
+        <MaterialCommunityIcons name="content-copy" size={20} color={Colors.DARK_BLUE} />
+      </TouchableOpacity>
+      <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('delete')}>
+        <Text style={styles.optionText}>Delete</Text>
+        <MaterialCommunityIcons name="delete" size={20} color={Colors.DARK_BLUE} />
+      </TouchableOpacity>
+      <TouchableOpacity style={styles.optionButton} onPress={() => handleOptionPress('pin')}>
+        <Text style={styles.optionText}>Pin message</Text>
+        <MaterialCommunityIcons name="pin" size={20} color={Colors.DARK_BLUE} />
+      </TouchableOpacity>
+    </Animated.View>
+  );
+
+const styles = StyleSheet.create({
+  optionsMenu: {
+    position: 'absolute',
+    backgroundColor: 'rgba(255, 255, 255, 0.9)',
+    borderRadius: 10,
+    padding: 8,
+    shadowColor: '#000',
+    shadowOpacity: 0.3,
+    shadowOffset: { width: 0, height: 2 },
+    shadowRadius: 5,
+    elevation: 5,
+    width: Dimensions.get('window').width * 0.75
+  },
+  optionButton: {
+    paddingVertical: 10,
+    paddingHorizontal: 12,
+    flexDirection: 'row',
+    justifyContent: 'space-between'
+  },
+  optionText: {
+    fontSize: 16
+  }
+});
+
+export default OptionsMenu;

+ 91 - 0
src/screens/InAppScreens/MessagesScreen/Components/ReactionBar.tsx

@@ -0,0 +1,91 @@
+import React from 'react';
+import { View, TouchableOpacity, Text, StyleSheet, Dimensions } from 'react-native';
+import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { Colors } from 'src/theme';
+
+interface MessagePosition {
+  x: number;
+  y: number;
+  isMine: boolean;
+  width: number;
+  height: number;
+}
+
+interface ReactionBarProps {
+  messagePosition: MessagePosition | null;
+  selectedMessage: any;
+  reactionEmojis: string[];
+  handleReactionPress: (emoji: string, messageId: number) => void;
+  openEmojiSelector: () => void;
+}
+
+const ReactionBar: React.FC<ReactionBarProps> = ({
+  messagePosition,
+  selectedMessage,
+  reactionEmojis,
+  handleReactionPress,
+  openEmojiSelector
+}) =>
+  selectedMessage &&
+  messagePosition && (
+    <Animated.View
+      entering={FadeIn}
+      exiting={FadeOut}
+      style={[
+        styles.reactionBar,
+        {
+          top: messagePosition.y - 50, // - reaction bar height
+          left: messagePosition.isMine
+            ? Dimensions.get('window').width - Dimensions.get('window').width * 0.75 - 8 // reaction bar width
+            : messagePosition.x
+        }
+      ]}
+    >
+      {reactionEmojis.map((emoji) => (
+        <TouchableOpacity
+          key={emoji}
+          onPress={() => handleReactionPress(emoji, selectedMessage?.currentMessage?._id)}
+        >
+          <Text style={styles.reactionEmoji}>{emoji}</Text>
+        </TouchableOpacity>
+      ))}
+      <TouchableOpacity onPress={() => openEmojiSelector()} style={styles.addReactionButton}>
+        <MaterialCommunityIcons name="plus" size={28} color={Colors.DARK_BLUE} />
+      </TouchableOpacity>
+    </Animated.View>
+  );
+
+const styles = StyleSheet.create({
+  reactionBar: {
+    position: 'absolute',
+    width: Dimensions.get('window').width * 0.75,
+    flexDirection: 'row',
+    backgroundColor: 'rgba(255, 255, 255, 0.9)',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    borderRadius: 20,
+    padding: 5,
+    paddingHorizontal: 12,
+    shadowColor: '#000',
+    shadowOpacity: 0.3,
+    shadowOffset: { width: 0, height: 2 },
+    shadowRadius: 5,
+    elevation: 5
+  },
+  reactionEmoji: {
+    fontSize: 28
+  },
+  addReactionButton: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: Colors.FILL_LIGHT,
+    borderRadius: 15,
+    borderWidth: 1,
+    borderColor: Colors.BORDER_LIGHT,
+    width: 30,
+    height: 30
+  }
+});
+
+export default ReactionBar;

+ 19 - 9
src/screens/InAppScreens/MessagesScreen/Components/SwipeableRow.tsx

@@ -10,7 +10,7 @@ import ArchiveIcon from 'assets/icons/messages/archive.svg';
 import DotsIcon from 'assets/icons/messages/dots.svg';
 import UnpinIcon from 'assets/icons/messages/unpin.svg';
 import { ChatProps } from '../types';
-import { usePostSetPinMutation } from '@api/chat/queries/use-post-set-pin';
+import { usePostSetArchiveMutation, usePostSetPinMutation } from '@api/chat';
 
 interface AppleStyleSwipeableRowProps extends PropsWithChildren<unknown> {
   chat: ChatProps;
@@ -27,6 +27,7 @@ const SwipeableRow: React.FC<AppleStyleSwipeableRowProps> = ({
   const swipeableRow = useRef<Swipeable>(null);
 
   const { mutateAsync: pinChat } = usePostSetPinMutation();
+  const { mutateAsync: archiveChat } = usePostSetArchiveMutation();
 
   const close = () => {
     swipeableRow.current?.close();
@@ -43,7 +44,7 @@ const SwipeableRow: React.FC<AppleStyleSwipeableRowProps> = ({
       outputRange: [x, 0]
     });
 
-    const pressHandler = () => {
+    const pressHandler = async () => {
       close();
       if (text === 'More') {
         SheetManager.show('more-modal', {
@@ -53,6 +54,12 @@ const SwipeableRow: React.FC<AppleStyleSwipeableRowProps> = ({
             avatar: chat.avatar
           } as any
         });
+      } else {
+        await archiveChat({
+          value: chat.archive === 1 ? 0 : 1,
+          conversation_with_user: chat.id
+        });
+        refetch();
       }
     };
 
@@ -83,12 +90,10 @@ const SwipeableRow: React.FC<AppleStyleSwipeableRowProps> = ({
 
     const pressHandler = async () => {
       close();
-      await pinChat(
-        {
-          value: chat.pin === 1 ? 0 : 1,
-          conversation_with_user: chat.id
-        }
-      );
+      await pinChat({
+        value: chat.pin === 1 ? 0 : 1,
+        conversation_with_user: chat.id
+      });
       refetch();
     };
 
@@ -109,7 +114,12 @@ const SwipeableRow: React.FC<AppleStyleSwipeableRowProps> = ({
         flexDirection: 'row'
       }}
     >
-      {renderRightAction('Archive', Colors.BORDER_LIGHT, 168, progress)}
+      {renderRightAction(
+        chat.archive === 0 ? 'Archive' : 'Unarchive',
+        Colors.BORDER_LIGHT,
+        168,
+        progress
+      )}
       {renderRightAction('More', Colors.FILL_LIGHT, 112, progress)}
     </View>
   );

+ 9 - 3
src/screens/InAppScreens/MessagesScreen/index.tsx

@@ -40,9 +40,9 @@ type Routes = {
 const MessagesScreen = () => {
   const navigation = useNavigation();
   const token = storage.get('token', StoreType.STRING) as string;
-  const { data: chatsData, refetch } = usePostGetChatsListQuery(token, 0, true);
   const [chats, setChats] = useState<Chat[]>([]);
   const [index, setIndex] = useState(0);
+  const { data: chatsData, refetch } = usePostGetChatsListQuery(token, index === 2 ? 1 : 0, true);
   const [filteredChats, setFilteredChats] = useState<Chat[]>([]);
   const [search, setSearch] = useState('');
   const openRowRef = useRef<any>(null);
@@ -93,7 +93,7 @@ const MessagesScreen = () => {
           .sort((a: Chat, b: Chat) => b.pin - a.pin || b.pin_order - a.pin_order)
       );
     } else if (index === 2) {
-      setFilteredChats([]);
+      setFilteredChats(chats.sort((a: Chat, b: Chat) => b.pin - a.pin || b.pin_order - a.pin_order));
     } else {
       setFilteredChats(
         chats.sort((a: Chat, b: Chat) => b.pin - a.pin || b.pin_order - a.pin_order)
@@ -150,7 +150,13 @@ const MessagesScreen = () => {
   const renderChatItem = ({ item }: { item: Chat }) => {
     return (
       <SwipeableRow
-        chat={{ id: item.uid, name: item.name, avatar: item.avatar, pin: item.pin }}
+        chat={{
+          id: item.uid,
+          name: item.name,
+          avatar: item.avatar,
+          pin: item.pin,
+          archive: item.archive
+        }}
         onRowOpen={handleRowOpen}
         refetch={refetch}
       >

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

@@ -21,6 +21,7 @@ export type ChatProps = {
   name: string;
   avatar: string | null;
   pin: 0 | 1;
+  archive: 0 | 1;
 };
 
 export type MessageSimple = {