|
@@ -17,7 +17,6 @@ import {
|
|
GiftedChat,
|
|
GiftedChat,
|
|
Bubble,
|
|
Bubble,
|
|
InputToolbar,
|
|
InputToolbar,
|
|
- Actions,
|
|
|
|
IMessage,
|
|
IMessage,
|
|
Send,
|
|
Send,
|
|
BubbleProps,
|
|
BubbleProps,
|
|
@@ -26,13 +25,7 @@ import {
|
|
MessageProps
|
|
MessageProps
|
|
} from 'react-native-gifted-chat';
|
|
} from 'react-native-gifted-chat';
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
-import * as ImagePicker from 'expo-image-picker';
|
|
|
|
-import { useActionSheet } from '@expo/react-native-action-sheet';
|
|
|
|
-import {
|
|
|
|
- GestureHandlerRootView,
|
|
|
|
- LongPressGestureHandler,
|
|
|
|
- Swipeable
|
|
|
|
-} from 'react-native-gesture-handler';
|
|
|
|
|
|
+import { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';
|
|
import { AvatarWithInitials, Header, WarningModal } from 'src/components';
|
|
import { AvatarWithInitials, Header, WarningModal } from 'src/components';
|
|
import { Colors } from 'src/theme';
|
|
import { Colors } from 'src/theme';
|
|
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
|
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
|
@@ -55,7 +48,6 @@ import {
|
|
} from '@api/chat';
|
|
} from '@api/chat';
|
|
import { CustomMessage, Message, Reaction } from '../types';
|
|
import { CustomMessage, Message, Reaction } from '../types';
|
|
import { API_HOST, WEBSOCKET_URL } from 'src/constants';
|
|
import { API_HOST, WEBSOCKET_URL } from 'src/constants';
|
|
-import { getFontSize } from 'src/utils';
|
|
|
|
import ReactionBar from '../Components/ReactionBar';
|
|
import ReactionBar from '../Components/ReactionBar';
|
|
import OptionsMenu from '../Components/OptionsMenu';
|
|
import OptionsMenu from '../Components/OptionsMenu';
|
|
import EmojiSelectorModal from '../Components/EmojiSelectorModal';
|
|
import EmojiSelectorModal from '../Components/EmojiSelectorModal';
|
|
@@ -63,9 +55,9 @@ import { styles } from './styles';
|
|
import SendIcon from 'assets/icons/messages/send.svg';
|
|
import SendIcon from 'assets/icons/messages/send.svg';
|
|
import { SheetManager } from 'react-native-actions-sheet';
|
|
import { SheetManager } from 'react-native-actions-sheet';
|
|
import { NAVIGATION_PAGES } from 'src/types';
|
|
import { NAVIGATION_PAGES } from 'src/types';
|
|
-import * as Notifications from 'expo-notifications';
|
|
|
|
import { usePushNotification } from 'src/contexts/PushNotificationContext';
|
|
import { usePushNotification } from 'src/contexts/PushNotificationContext';
|
|
import ReactionsListModal from '../Components/ReactionsListModal';
|
|
import ReactionsListModal from '../Components/ReactionsListModal';
|
|
|
|
+import { dismissChatNotifications } from '../utils';
|
|
|
|
|
|
const options = {
|
|
const options = {
|
|
enableVibrateFallback: true,
|
|
enableVibrateFallback: true,
|
|
@@ -75,14 +67,15 @@ const options = {
|
|
const reactionEmojis = ['👍', '❤️', '😂', '😮', '😭'];
|
|
const reactionEmojis = ['👍', '❤️', '😂', '😮', '😭'];
|
|
|
|
|
|
const ChatScreen = ({ route }: { route: any }) => {
|
|
const ChatScreen = ({ route }: { route: any }) => {
|
|
|
|
+ const token = storage.get('token', StoreType.STRING) as string;
|
|
const { id, name, avatar }: { id: number; name: string; avatar: string | null } = route.params;
|
|
const { id, name, avatar }: { id: number; name: string; avatar: string | null } = route.params;
|
|
|
|
+
|
|
const currentUserId = storage.get('uid', StoreType.STRING) as number;
|
|
const currentUserId = storage.get('uid', StoreType.STRING) as number;
|
|
- const token = storage.get('token', StoreType.STRING) as string;
|
|
|
|
const insets = useSafeAreaInsets();
|
|
const insets = useSafeAreaInsets();
|
|
const [messages, setMessages] = useState<CustomMessage[] | null>();
|
|
const [messages, setMessages] = useState<CustomMessage[] | null>();
|
|
- const { showActionSheetWithOptions } = useActionSheet();
|
|
|
|
const navigation = useNavigation();
|
|
const navigation = useNavigation();
|
|
- const { data: chatData, isFetching, refetch } = usePostGetChatWithQuery(token, id, -1, -1, true);
|
|
|
|
|
|
+ const [prevThenMessageId, setPrevThenMessageId] = useState<number>(-1);
|
|
|
|
+ const { data: chatData } = usePostGetChatWithQuery(token, id, 50, prevThenMessageId, true); // to do cache
|
|
const { mutateAsync: sendMessage } = usePostSendMessageMutation();
|
|
const { mutateAsync: sendMessage } = usePostSendMessageMutation();
|
|
|
|
|
|
const swipeableRowRef = useRef<Swipeable | null>(null);
|
|
const swipeableRowRef = useRef<Swipeable | null>(null);
|
|
@@ -121,6 +114,8 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
const flatList = useRef<FlatList | null>(null);
|
|
const flatList = useRef<FlatList | null>(null);
|
|
const scrollY = useSharedValue(0);
|
|
const scrollY = useSharedValue(0);
|
|
const { isSubscribed } = usePushNotification();
|
|
const { isSubscribed } = usePushNotification();
|
|
|
|
+ const [isLoadingEarlier, setIsLoadingEarlier] = useState(false);
|
|
|
|
+ const [hasMoreMessages, setHasMoreMessages] = useState(true);
|
|
|
|
|
|
const socket = useRef<WebSocket | null>(null);
|
|
const socket = useRef<WebSocket | null>(null);
|
|
|
|
|
|
@@ -128,103 +123,11 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
setModalInfo({ ...modalInfo, visible: false });
|
|
setModalInfo({ ...modalInfo, visible: false });
|
|
};
|
|
};
|
|
|
|
|
|
- const dismissChatNotifications = async (chatWithUserId: number) => {
|
|
|
|
- const { status } = await Notifications.getPermissionsAsync();
|
|
|
|
- if (status !== 'granted' || !isSubscribed) {
|
|
|
|
- setModalInfo({
|
|
|
|
- visible: true,
|
|
|
|
- type: 'success',
|
|
|
|
- message:
|
|
|
|
- 'To use this feature we need your permission to access your notifications. You will be redirected to the notification settings screen where you need to enable them.',
|
|
|
|
- action: () =>
|
|
|
|
- // @ts-ignore
|
|
|
|
- navigation.navigate(NAVIGATION_PAGES.MENU_DRAWER, {
|
|
|
|
- screen: NAVIGATION_PAGES.NOTIFICATIONS
|
|
|
|
- })
|
|
|
|
- });
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const getNotificationData = (notification: Notifications.Notification) => {
|
|
|
|
- if (Platform.OS === 'android') {
|
|
|
|
- const data = notification.request.content.data;
|
|
|
|
- if (data?.params) {
|
|
|
|
- try {
|
|
|
|
- return JSON.parse(data.params) ?? {};
|
|
|
|
- } catch (error) {
|
|
|
|
- console.error('Error parsing params:', error);
|
|
|
|
- return {};
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- Notifications.dismissNotificationAsync(notification.request.identifier);
|
|
|
|
- return {};
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- const data = (notification.request.trigger as Notifications.PushNotificationTrigger)
|
|
|
|
- ?.payload;
|
|
|
|
- if (data?.params) {
|
|
|
|
- try {
|
|
|
|
- return JSON.parse(data.params as string) ?? {};
|
|
|
|
- } catch (error) {
|
|
|
|
- console.error('Error parsing params:', error);
|
|
|
|
- return {};
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const clearNotificationsFromUser = async (userId: number) => {
|
|
|
|
- const presentedNotifications = await Notifications.getPresentedNotificationsAsync();
|
|
|
|
- presentedNotifications.forEach((notification) => {
|
|
|
|
- const parsedParams = getNotificationData(notification);
|
|
|
|
- const conversation_with_user = parsedParams?.id;
|
|
|
|
-
|
|
|
|
- if (conversation_with_user === userId) {
|
|
|
|
- Notifications.dismissNotificationAsync(notification.request.identifier);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- await clearNotificationsFromUser(chatWithUserId);
|
|
|
|
-
|
|
|
|
- Notifications.setNotificationHandler({
|
|
|
|
- handleNotification: async (notification) => {
|
|
|
|
- let conversation_with_user = 0;
|
|
|
|
- const parsedParams = getNotificationData(notification);
|
|
|
|
- conversation_with_user = parsedParams?.id;
|
|
|
|
-
|
|
|
|
- if (conversation_with_user === chatWithUserId) {
|
|
|
|
- return {
|
|
|
|
- shouldShowAlert: false,
|
|
|
|
- shouldPlaySound: false,
|
|
|
|
- shouldSetBadge: false
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return {
|
|
|
|
- shouldShowAlert: true,
|
|
|
|
- shouldPlaySound: false,
|
|
|
|
- shouldSetBadge: false
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- return () => {
|
|
|
|
- Notifications.setNotificationHandler({
|
|
|
|
- handleNotification: async () => ({
|
|
|
|
- shouldShowAlert: true,
|
|
|
|
- shouldPlaySound: false,
|
|
|
|
- shouldSetBadge: false
|
|
|
|
- })
|
|
|
|
- });
|
|
|
|
- };
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
let unsubscribe: any;
|
|
let unsubscribe: any;
|
|
|
|
|
|
const setupNotificationHandler = async () => {
|
|
const setupNotificationHandler = async () => {
|
|
- unsubscribe = await dismissChatNotifications(id);
|
|
|
|
|
|
+ unsubscribe = await dismissChatNotifications(id, isSubscribed, setModalInfo, navigation);
|
|
};
|
|
};
|
|
|
|
|
|
setupNotificationHandler();
|
|
setupNotificationHandler();
|
|
@@ -251,40 +154,188 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
};
|
|
};
|
|
|
|
|
|
return () => {
|
|
return () => {
|
|
- socket.current?.close();
|
|
|
|
|
|
+ if (socket.current) {
|
|
|
|
+ socket.current.close();
|
|
|
|
+ socket.current = null;
|
|
|
|
+ }
|
|
};
|
|
};
|
|
}, [token]);
|
|
}, [token]);
|
|
|
|
|
|
const handleWebSocketMessage = (data: any) => {
|
|
const handleWebSocketMessage = (data: any) => {
|
|
switch (data.action) {
|
|
switch (data.action) {
|
|
case 'new_message':
|
|
case 'new_message':
|
|
- if (data.conversation_with === id) {
|
|
|
|
- refetch();
|
|
|
|
|
|
+ if (data.conversation_with === id && data.message) {
|
|
|
|
+ const newMessage = mapApiMessageToGiftedMessage(data.message);
|
|
|
|
+ setMessages((previousMessages) => {
|
|
|
|
+ const messageExists =
|
|
|
|
+ previousMessages && previousMessages.some((msg) => msg._id === newMessage._id);
|
|
|
|
+ if (!messageExists) {
|
|
|
|
+ return GiftedChat.append(previousMessages ?? [], [newMessage]);
|
|
|
|
+ }
|
|
|
|
+ return previousMessages;
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'new_reaction':
|
|
|
|
+ if (data.conversation_with === id && data.reaction) {
|
|
|
|
+ updateMessageWithReaction(data.reaction);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'unreact':
|
|
|
|
+ if (data.conversation_with === id && data.unreacted_message_id) {
|
|
|
|
+ removeReactionFromMessage(data.unreacted_message_id);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'delete_message':
|
|
|
|
+ if (data.conversation_with === id && data.deleted_message_id) {
|
|
|
|
+ removeDeletedMessage(data.deleted_message_id);
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
|
|
+
|
|
case 'is_typing':
|
|
case 'is_typing':
|
|
if (data.conversation_with === id) {
|
|
if (data.conversation_with === id) {
|
|
setIsTyping(true);
|
|
setIsTyping(true);
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
|
|
+
|
|
case 'stopped_typing':
|
|
case 'stopped_typing':
|
|
if (data.conversation_with === id) {
|
|
if (data.conversation_with === id) {
|
|
setIsTyping(false);
|
|
setIsTyping(false);
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
- case 'new_reaction':
|
|
|
|
- if (data.conversation_with === id) {
|
|
|
|
- refetch();
|
|
|
|
|
|
+
|
|
|
|
+ case 'messages_read':
|
|
|
|
+ if (data.conversation_with === id && data.read_messages_ids) {
|
|
|
|
+ setMessages(
|
|
|
|
+ (prevMessages) =>
|
|
|
|
+ prevMessages?.map((msg) => {
|
|
|
|
+ if (data.read_messages_ids.includes(msg._id)) {
|
|
|
|
+ return { ...msg, received: true };
|
|
|
|
+ }
|
|
|
|
+ return msg;
|
|
|
|
+ }) ?? []
|
|
|
|
+ );
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
|
|
+
|
|
default:
|
|
default:
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
- const sendWebSocketMessage = (action: string) => {
|
|
|
|
|
|
+ const updateMessageWithReaction = (reactionData: any) => {
|
|
|
|
+ setMessages(
|
|
|
|
+ (prevMessages) =>
|
|
|
|
+ prevMessages?.map((msg) => {
|
|
|
|
+ if (msg._id === reactionData.message_id) {
|
|
|
|
+ const updatedReactions = [
|
|
|
|
+ ...(Array.isArray(msg.reactions)
|
|
|
|
+ ? msg.reactions?.filter((r: any) => r.uid !== reactionData.uid)
|
|
|
|
+ : []),
|
|
|
|
+ reactionData
|
|
|
|
+ ];
|
|
|
|
+ return { ...msg, reactions: updatedReactions };
|
|
|
|
+ }
|
|
|
|
+ return msg;
|
|
|
|
+ }) ?? []
|
|
|
|
+ );
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const removeReactionFromMessage = (messageId: number) => {
|
|
|
|
+ setMessages(
|
|
|
|
+ (prevMessages) =>
|
|
|
|
+ prevMessages?.map((msg) => {
|
|
|
|
+ if (msg._id === messageId) {
|
|
|
|
+ const updatedReactions = Array.isArray(msg.reactions)
|
|
|
|
+ ? msg.reactions?.filter((r: any) => r.uid !== id)
|
|
|
|
+ : [];
|
|
|
|
+ return { ...msg, reactions: updatedReactions };
|
|
|
|
+ }
|
|
|
|
+ return msg;
|
|
|
|
+ }) ?? []
|
|
|
|
+ );
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const removeDeletedMessage = (messageId: number) => {
|
|
|
|
+ setMessages(
|
|
|
|
+ (prevMessages) =>
|
|
|
|
+ prevMessages?.map((msg) => {
|
|
|
|
+ if (msg._id === messageId) {
|
|
|
|
+ return {
|
|
|
|
+ ...msg,
|
|
|
|
+ deleted: true,
|
|
|
|
+ text: 'This message was deleted',
|
|
|
|
+ pending: false,
|
|
|
|
+ sent: false,
|
|
|
|
+ received: false
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ return msg;
|
|
|
|
+ }) ?? []
|
|
|
|
+ );
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ useEffect(() => {
|
|
|
|
+ const pingInterval = setInterval(() => {
|
|
|
|
+ if (socket.current && socket.current.readyState === WebSocket.OPEN) {
|
|
|
|
+ socket.current.send(JSON.stringify({ action: 'ping', conversation_with: id }));
|
|
|
|
+ }
|
|
|
|
+ }, 50000);
|
|
|
|
+
|
|
|
|
+ return () => clearInterval(pingInterval);
|
|
|
|
+ }, []);
|
|
|
|
+
|
|
|
|
+ const sendWebSocketMessage = (
|
|
|
|
+ action: string,
|
|
|
|
+ message: CustomMessage | null = null,
|
|
|
|
+ reaction: string | null = null,
|
|
|
|
+ readMessagesIds: number[] | null = null
|
|
|
|
+ ) => {
|
|
if (socket.current && socket.current.readyState === WebSocket.OPEN) {
|
|
if (socket.current && socket.current.readyState === WebSocket.OPEN) {
|
|
- socket.current.send(JSON.stringify({ action, conversation_with: id }));
|
|
|
|
|
|
+ const data: any = {
|
|
|
|
+ action,
|
|
|
|
+ conversation_with: id
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ if (action === 'new_message' && message) {
|
|
|
|
+ data.message = {
|
|
|
|
+ id: message._id,
|
|
|
|
+ text: message.text,
|
|
|
|
+ sender: +currentUserId,
|
|
|
|
+ sent_datetime: new Date().toISOString().replace('T', ' ').substring(0, 19),
|
|
|
|
+ reply_to_id: message.replyMessage?.id ?? -1,
|
|
|
|
+ reply_to: message.replyMessage ?? null,
|
|
|
|
+ reactions: message.reactions ?? '{}',
|
|
|
|
+ status: 2,
|
|
|
|
+ attachement: -1
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (action === 'new_reaction' && message && reaction) {
|
|
|
|
+ data.reaction = {
|
|
|
|
+ message_id: message._id,
|
|
|
|
+ reaction,
|
|
|
|
+ uid: +currentUserId,
|
|
|
|
+ datetime: new Date().toISOString()
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (action === 'unreact' && message) {
|
|
|
|
+ data.message_id = message._id;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (action === 'delete_message' && message) {
|
|
|
|
+ data.message_id = message._id;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (action === 'messages_read' && readMessagesIds) {
|
|
|
|
+ data.messages_ids = readMessagesIds;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ socket.current.send(JSON.stringify(data));
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
@@ -346,11 +397,35 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
setUnreadMessageIndex(0);
|
|
setUnreadMessageIndex(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- setMessages(mappedMessages);
|
|
|
|
|
|
+
|
|
|
|
+ setMessages((previousMessages) => {
|
|
|
|
+ const newMessages = mappedMessages.filter(
|
|
|
|
+ (newMsg) => !previousMessages?.some((oldMsg) => oldMsg._id === newMsg._id)
|
|
|
|
+ );
|
|
|
|
+ return prevThenMessageId !== -1 && previousMessages
|
|
|
|
+ ? GiftedChat.prepend(previousMessages, newMessages)
|
|
|
|
+ : mappedMessages;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (mappedMessages.length < 50) {
|
|
|
|
+ setHasMoreMessages(false);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ setIsLoadingEarlier(false);
|
|
}
|
|
}
|
|
}, [chatData])
|
|
}, [chatData])
|
|
);
|
|
);
|
|
|
|
|
|
|
|
+ const loadEarlierMessages = async () => {
|
|
|
|
+ if (!hasMoreMessages || isLoadingEarlier || !messages) return;
|
|
|
|
+
|
|
|
|
+ setIsLoadingEarlier(true);
|
|
|
|
+
|
|
|
|
+ const previousMessageId = messages[messages.length - 1]._id;
|
|
|
|
+
|
|
|
|
+ setPrevThenMessageId(previousMessageId);
|
|
|
|
+ };
|
|
|
|
+
|
|
const sentToServer = useRef<Set<number>>(new Set());
|
|
const sentToServer = useRef<Set<number>>(new Set());
|
|
|
|
|
|
const handleViewableItemsChanged = ({ viewableItems }: { viewableItems: any[] }) => {
|
|
const handleViewableItemsChanged = ({ viewableItems }: { viewableItems: any[] }) => {
|
|
@@ -373,10 +448,9 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
messages_id: newViewableUnreadMessages
|
|
messages_id: newViewableUnreadMessages
|
|
},
|
|
},
|
|
{
|
|
{
|
|
- onSuccess: (res) => {
|
|
|
|
|
|
+ onSuccess: () => {
|
|
newViewableUnreadMessages.forEach((id) => sentToServer.current.add(id));
|
|
newViewableUnreadMessages.forEach((id) => sentToServer.current.add(id));
|
|
- // sendWebSocketMessage('messages_read');
|
|
|
|
- sendWebSocketMessage('new_message');
|
|
|
|
|
|
+ sendWebSocketMessage('messages_read', null, null, newViewableUnreadMessages);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
);
|
|
@@ -467,12 +541,26 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
},
|
|
},
|
|
{
|
|
{
|
|
onSuccess: () => {
|
|
onSuccess: () => {
|
|
- setMessages((prevMessages) =>
|
|
|
|
- prevMessages ? prevMessages.filter((msg) => msg._id !== messageId) : []
|
|
|
|
|
|
+ setMessages(
|
|
|
|
+ (prevMessages) =>
|
|
|
|
+ prevMessages?.map((msg) => {
|
|
|
|
+ if (msg._id === messageId) {
|
|
|
|
+ return {
|
|
|
|
+ ...msg,
|
|
|
|
+ deleted: true,
|
|
|
|
+ text: 'This message was deleted',
|
|
|
|
+ pending: false,
|
|
|
|
+ sent: false,
|
|
|
|
+ received: false
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ return msg;
|
|
|
|
+ }) ?? []
|
|
);
|
|
);
|
|
- // sendWebSocketMessage('message_deleted');
|
|
|
|
- refetch();
|
|
|
|
- sendWebSocketMessage('new_message');
|
|
|
|
|
|
+ const messageToDelete = messages?.find((msg) => msg._id === messageId);
|
|
|
|
+ if (messageToDelete) {
|
|
|
|
+ sendWebSocketMessage('delete_message', messageToDelete, null, null);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
);
|
|
@@ -534,30 +622,20 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
|
|
|
return (
|
|
return (
|
|
<View
|
|
<View
|
|
- style={{
|
|
|
|
- flexDirection: 'row',
|
|
|
|
- justifyContent: hasReactions ? 'space-between' : 'flex-end',
|
|
|
|
- alignItems: 'center',
|
|
|
|
- paddingHorizontal: 8,
|
|
|
|
- paddingBottom: 6,
|
|
|
|
- flexShrink: 1,
|
|
|
|
- flexGrow: 1,
|
|
|
|
- gap: 12
|
|
|
|
- }}
|
|
|
|
|
|
+ style={[
|
|
|
|
+ styles.bottomContainer,
|
|
|
|
+ {
|
|
|
|
+ justifyContent: hasReactions ? 'space-between' : 'flex-end'
|
|
|
|
+ }
|
|
|
|
+ ]}
|
|
>
|
|
>
|
|
{hasReactions && (
|
|
{hasReactions && (
|
|
<TouchableOpacity
|
|
<TouchableOpacity
|
|
style={[
|
|
style={[
|
|
|
|
+ styles.bottomCustomContainer,
|
|
{
|
|
{
|
|
- flexDirection: 'row',
|
|
|
|
- alignItems: 'center',
|
|
|
|
- flexShrink: 0,
|
|
|
|
backgroundColor:
|
|
backgroundColor:
|
|
- time.position === 'left' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)',
|
|
|
|
- borderRadius: 12,
|
|
|
|
- paddingHorizontal: 6,
|
|
|
|
- paddingVertical: 4,
|
|
|
|
- gap: 6
|
|
|
|
|
|
+ time.position === 'left' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'
|
|
}
|
|
}
|
|
]}
|
|
]}
|
|
onPress={() =>
|
|
onPress={() =>
|
|
@@ -597,25 +675,8 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
})}
|
|
})}
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
)}
|
|
)}
|
|
- <View
|
|
|
|
- style={{
|
|
|
|
- flexDirection: 'row',
|
|
|
|
- gap: 4,
|
|
|
|
- alignItems: 'center',
|
|
|
|
- alignSelf: 'flex-end'
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
- <Text
|
|
|
|
- style={{
|
|
|
|
- color: Colors.LIGHT_GRAY,
|
|
|
|
- fontSize: getFontSize(10),
|
|
|
|
- fontWeight: '600',
|
|
|
|
- paddingLeft: 8,
|
|
|
|
- flexShrink: 0
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
- {formattedTime}
|
|
|
|
- </Text>
|
|
|
|
|
|
+ <View style={styles.timeContainer}>
|
|
|
|
+ <Text style={styles.timeText}>{formattedTime}</Text>
|
|
{renderTicks(time.currentMessage)}
|
|
{renderTicks(time.currentMessage)}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
@@ -678,6 +739,8 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
}
|
|
}
|
|
const message = { ...newMessages[0], pending: true };
|
|
const message = { ...newMessages[0], pending: true };
|
|
|
|
|
|
|
|
+ setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [message]));
|
|
|
|
+
|
|
sendMessage(
|
|
sendMessage(
|
|
{
|
|
{
|
|
token,
|
|
token,
|
|
@@ -686,118 +749,29 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
reply_to_id: replyMessage ? (replyMessage._id as number) : -1
|
|
reply_to_id: replyMessage ? (replyMessage._id as number) : -1
|
|
},
|
|
},
|
|
{
|
|
{
|
|
- onSuccess: () => sendWebSocketMessage('new_message'),
|
|
|
|
|
|
+ onSuccess: (res) => {
|
|
|
|
+ const newMessage = {
|
|
|
|
+ _id: res.message_id,
|
|
|
|
+ text: message.text,
|
|
|
|
+ replyMessage: { ...message.replyMessage, sender: replyMessage?.user?._id }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ setMessages((previousMessages) =>
|
|
|
|
+ (previousMessages ?? []).map((msg) =>
|
|
|
|
+ msg._id === message._id ? { ...msg, _id: res.message_id } : msg
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
|
|
|
|
+ },
|
|
onError: (err) => console.log('err', err)
|
|
onError: (err) => console.log('err', err)
|
|
}
|
|
}
|
|
);
|
|
);
|
|
|
|
|
|
- setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [message]));
|
|
|
|
clearReplyMessage();
|
|
clearReplyMessage();
|
|
},
|
|
},
|
|
[replyMessage]
|
|
[replyMessage]
|
|
);
|
|
);
|
|
|
|
|
|
- const openActionSheet = () => {
|
|
|
|
- const options = ['Open Camera', 'Select from gallery', 'Cancel'];
|
|
|
|
- const cancelButtonIndex = 2;
|
|
|
|
-
|
|
|
|
- showActionSheetWithOptions(
|
|
|
|
- {
|
|
|
|
- options,
|
|
|
|
- cancelButtonIndex
|
|
|
|
- },
|
|
|
|
- async (buttonIndex) => {
|
|
|
|
- if (buttonIndex === 0) {
|
|
|
|
- openCamera();
|
|
|
|
- } else if (buttonIndex === 1) {
|
|
|
|
- openGallery();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- );
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const openCamera = async () => {
|
|
|
|
- const permissionResult = await ImagePicker.requestCameraPermissionsAsync();
|
|
|
|
-
|
|
|
|
- if (permissionResult.granted === false) {
|
|
|
|
- alert('Permission denied to access camera');
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const result = await ImagePicker.launchCameraAsync({
|
|
|
|
- mediaTypes: ImagePicker.MediaTypeOptions.All,
|
|
|
|
- quality: 1,
|
|
|
|
- allowsEditing: true
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- if (!result.canceled) {
|
|
|
|
- const newMedia = {
|
|
|
|
- _id: Date.now().toString(),
|
|
|
|
- createdAt: new Date(),
|
|
|
|
- user: { _id: +currentUserId, name: 'Me' },
|
|
|
|
- image: result.assets[0].type === 'image' ? result.assets[0].uri : null,
|
|
|
|
- video: result.assets[0].type === 'video' ? result.assets[0].uri : null
|
|
|
|
- };
|
|
|
|
- setMessages((previousMessages) =>
|
|
|
|
- GiftedChat.append(previousMessages ?? [], [newMedia as any])
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const openGallery = async () => {
|
|
|
|
- const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
|
|
-
|
|
|
|
- if (permissionResult.granted === false) {
|
|
|
|
- alert('Denied');
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const result = await ImagePicker.launchImageLibraryAsync({
|
|
|
|
- mediaTypes: ImagePicker.MediaTypeOptions.All,
|
|
|
|
- allowsMultipleSelection: true,
|
|
|
|
- quality: 1
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- if (!result.canceled && result.assets) {
|
|
|
|
- const imageMessages = result.assets.map((asset) => ({
|
|
|
|
- _id: Date.now().toString() + asset.uri,
|
|
|
|
- createdAt: new Date(),
|
|
|
|
- user: { _id: +currentUserId, name: 'Me' },
|
|
|
|
- image: asset.type === 'image' ? asset.uri : null,
|
|
|
|
- video: asset.type === 'video' ? asset.uri : null
|
|
|
|
- }));
|
|
|
|
-
|
|
|
|
- setMessages((previousMessages) =>
|
|
|
|
- GiftedChat.append(previousMessages ?? [], imageMessages as any[])
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const renderMessageVideo = (props: BubbleProps<CustomMessage>) => {
|
|
|
|
- const { currentMessage } = props;
|
|
|
|
-
|
|
|
|
- if (currentMessage.video) {
|
|
|
|
- return (
|
|
|
|
- <LongPressGestureHandler
|
|
|
|
- onHandlerStateChange={(event) => handleLongPress(currentMessage, props)}
|
|
|
|
- >
|
|
|
|
- <TouchableOpacity
|
|
|
|
- onPress={() => setSelectedMedia(currentMessage.video)}
|
|
|
|
- style={styles.mediaContainer}
|
|
|
|
- >
|
|
|
|
- <Video
|
|
|
|
- source={{ uri: currentMessage.video }}
|
|
|
|
- style={styles.chatMedia}
|
|
|
|
- useNativeControls
|
|
|
|
- />
|
|
|
|
- </TouchableOpacity>
|
|
|
|
- </LongPressGestureHandler>
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return null;
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
const addReaction = (messageId: number, reaction: string) => {
|
|
const addReaction = (messageId: number, reaction: string) => {
|
|
if (!messages) return;
|
|
if (!messages) return;
|
|
|
|
|
|
@@ -823,7 +797,12 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
reactToMessage(
|
|
reactToMessage(
|
|
{ token, message_id: messageId, reaction: reaction, conversation_with_user: id },
|
|
{ token, message_id: messageId, reaction: reaction, conversation_with_user: id },
|
|
{
|
|
{
|
|
- onSuccess: () => sendWebSocketMessage('new_reaction'),
|
|
|
|
|
|
+ onSuccess: () => {
|
|
|
|
+ const message = messages.find((msg) => msg._id === messageId);
|
|
|
|
+ if (message) {
|
|
|
|
+ sendWebSocketMessage('new_reaction', message, reaction);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
onError: (err) => console.log('err', err)
|
|
onError: (err) => console.log('err', err)
|
|
}
|
|
}
|
|
);
|
|
);
|
|
@@ -1063,14 +1042,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
const renderScrollToBottom = () => {
|
|
const renderScrollToBottom = () => {
|
|
return (
|
|
return (
|
|
<TouchableOpacity
|
|
<TouchableOpacity
|
|
- style={{
|
|
|
|
- position: 'absolute',
|
|
|
|
- bottom: -20,
|
|
|
|
- right: -20,
|
|
|
|
- backgroundColor: Colors.DARK_BLUE,
|
|
|
|
- borderRadius: 20,
|
|
|
|
- padding: 8
|
|
|
|
- }}
|
|
|
|
|
|
+ style={styles.scrollToBottom}
|
|
onPress={() => {
|
|
onPress={() => {
|
|
if (flatList.current) {
|
|
if (flatList.current) {
|
|
flatList.current.scrollToIndex({ index: 0, animated: true });
|
|
flatList.current.scrollToIndex({ index: 0, animated: true });
|
|
@@ -1164,15 +1136,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
onInputTextChanged={(text) => handleTyping(text.length > 0)}
|
|
onInputTextChanged={(text) => handleTyping(text.length > 0)}
|
|
isTyping={isTyping}
|
|
isTyping={isTyping}
|
|
renderSend={(props) => (
|
|
renderSend={(props) => (
|
|
- <View
|
|
|
|
- style={{
|
|
|
|
- flexDirection: 'row',
|
|
|
|
- height: '100%',
|
|
|
|
- alignItems: 'center',
|
|
|
|
- justifyContent: 'center',
|
|
|
|
- paddingHorizontal: 14
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
|
|
+ <View style={styles.sendBtn}>
|
|
{props.text?.trim() && (
|
|
{props.text?.trim() && (
|
|
<Send
|
|
<Send
|
|
{...props}
|
|
{...props}
|
|
@@ -1198,7 +1162,6 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
renderChatFooter={() => (
|
|
renderChatFooter={() => (
|
|
<ReplyMessageBar clearReply={clearReplyMessage} message={replyMessage} />
|
|
<ReplyMessageBar clearReply={clearReplyMessage} message={replyMessage} />
|
|
)}
|
|
)}
|
|
- // renderMessageVideo={renderMessageVideo}
|
|
|
|
renderAvatar={null}
|
|
renderAvatar={null}
|
|
maxComposerHeight={100}
|
|
maxComposerHeight={100}
|
|
renderComposer={(props) => <Composer {...props} />}
|
|
renderComposer={(props) => <Composer {...props} />}
|
|
@@ -1225,6 +1188,15 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]}
|
|
]}
|
|
|
|
+ infiniteScroll={true}
|
|
|
|
+ loadEarlier={hasMoreMessages}
|
|
|
|
+ isLoadingEarlier={isLoadingEarlier}
|
|
|
|
+ onLoadEarlier={loadEarlierMessages}
|
|
|
|
+ renderLoadEarlier={() => (
|
|
|
|
+ <View style={{ paddingVertical: 20 }}>
|
|
|
|
+ <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
|
|
|
|
+ </View>
|
|
|
|
+ )}
|
|
/>
|
|
/>
|
|
) : (
|
|
) : (
|
|
<ActivityIndicator size="large" color={Colors.DARK_BLUE} />
|
|
<ActivityIndicator size="large" color={Colors.DARK_BLUE} />
|