|
@@ -4,7 +4,6 @@ import {
|
|
|
TouchableOpacity,
|
|
|
Image,
|
|
|
Modal,
|
|
|
- StyleSheet,
|
|
|
Text,
|
|
|
FlatList,
|
|
|
Dimensions,
|
|
@@ -19,7 +18,9 @@ import {
|
|
|
IMessage,
|
|
|
Send,
|
|
|
BubbleProps,
|
|
|
- Composer
|
|
|
+ Composer,
|
|
|
+ TimeProps,
|
|
|
+ MessageProps
|
|
|
} from 'react-native-gifted-chat';
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
|
import * as ImagePicker from 'expo-image-picker';
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
LongPressGestureHandler,
|
|
|
Swipeable
|
|
|
} from 'react-native-gesture-handler';
|
|
|
-import { Header, PageWrapper } from 'src/components';
|
|
|
+import { Header } from 'src/components';
|
|
|
import { Colors } from 'src/theme';
|
|
|
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
|
|
import { Video } from 'expo-av';
|
|
@@ -37,7 +38,7 @@ import ChatMessageBox from '../Components/ChatMessageBox';
|
|
|
import ReplyMessageBar from '../Components/ReplyMessageBar';
|
|
|
import { useSharedValue, withTiming } from 'react-native-reanimated';
|
|
|
import { BlurView } from 'expo-blur';
|
|
|
-import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
+import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
import Clipboard from '@react-native-clipboard/clipboard';
|
|
|
import { trigger } from 'react-native-haptic-feedback';
|
|
|
import ReactModal from 'react-native-modal';
|
|
@@ -49,12 +50,13 @@ import {
|
|
|
usePostReactToMessageMutation,
|
|
|
usePostSendMessageMutation
|
|
|
} from '@api/chat';
|
|
|
-import { Message } from '../types';
|
|
|
+import { CustomMessage, Message, Reaction } 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';
|
|
|
+import { styles } from './styles';
|
|
|
|
|
|
const options = {
|
|
|
enableVibrateFallback: true,
|
|
@@ -68,7 +70,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
const currentUserId = storage.get('uid', StoreType.STRING) as number;
|
|
|
const token = storage.get('token', StoreType.STRING) as string;
|
|
|
const insets = useSafeAreaInsets();
|
|
|
- const [messages, setMessages] = useState<IMessage[]>([]);
|
|
|
+ const [messages, setMessages] = useState<CustomMessage[]>([]);
|
|
|
const { showActionSheetWithOptions } = useActionSheet();
|
|
|
const navigation = useNavigation();
|
|
|
const { data: chatData, isFetching, refetch } = usePostGetChatWithQuery(token, id, -1, -1, true);
|
|
@@ -76,11 +78,11 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
|
|
|
const swipeableRowRef = useRef<Swipeable | null>(null);
|
|
|
const messageContainerRef = useRef<FlatList<IMessage> | null>(null);
|
|
|
- const [selectedMedia, setSelectedMedia] = useState(null);
|
|
|
+ const [selectedMedia, setSelectedMedia] = useState<any>(null);
|
|
|
|
|
|
- const [replyMessage, setReplyMessage] = useState<IMessage | null>(null);
|
|
|
+ const [replyMessage, setReplyMessage] = useState<CustomMessage | null>(null);
|
|
|
|
|
|
- const [selectedMessage, setSelectedMessage] = useState<IMessage | null>(null);
|
|
|
+ const [selectedMessage, setSelectedMessage] = useState<BubbleProps<CustomMessage> | null>(null);
|
|
|
const [emojiSelectorVisible, setEmojiSelectorVisible] = useState(false);
|
|
|
const [messagePosition, setMessagePosition] = useState<{
|
|
|
x: number;
|
|
@@ -102,7 +104,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
const flatList = useRef<FlatList | null>(null);
|
|
|
const scrollY = useSharedValue(0);
|
|
|
|
|
|
- const mapApiMessageToGiftedMessage = (message: Message): IMessage => {
|
|
|
+ const mapApiMessageToGiftedMessage = (message: Message): CustomMessage => {
|
|
|
return {
|
|
|
_id: message.id,
|
|
|
text: message.text,
|
|
@@ -115,7 +117,8 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
message.reply_to_id !== -1
|
|
|
? {
|
|
|
text: message.reply_to.text,
|
|
|
- id: message.reply_to.id
|
|
|
+ id: message.reply_to.id,
|
|
|
+ name: message.reply_to.sender === id ? name : 'Me'
|
|
|
}
|
|
|
: null,
|
|
|
reactions: JSON.parse(message.reactions || '{}'),
|
|
@@ -194,7 +197,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
|
|
|
const clearReplyMessage = () => setReplyMessage(null);
|
|
|
|
|
|
- const handleLongPress = (message: IMessage, props) => {
|
|
|
+ const handleLongPress = (message: CustomMessage, props: BubbleProps<CustomMessage>) => {
|
|
|
const messageRef = messageRefs.current[message._id];
|
|
|
|
|
|
setSelectedMessage(props);
|
|
@@ -216,8 +219,8 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if (spaceBelow < 150) {
|
|
|
- const extraShift = 150 - spaceBelow;
|
|
|
+ if (spaceBelow < 210) {
|
|
|
+ const extraShift = 210 - spaceBelow;
|
|
|
finalY -= extraShift;
|
|
|
}
|
|
|
|
|
@@ -226,7 +229,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
finalY += extraShift;
|
|
|
}
|
|
|
|
|
|
- if (spaceBelow < 150 || spaceAbove < 50) {
|
|
|
+ if (spaceBelow < 210 || spaceAbove < 50) {
|
|
|
const targetY = screenHeight / 2 - height / 2;
|
|
|
scrollY.value = withTiming(finalY - finalY);
|
|
|
}
|
|
@@ -253,21 +256,18 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
};
|
|
|
|
|
|
const handleReactionPress = (emoji: string, messageId: number) => {
|
|
|
- if (emoji === '+') {
|
|
|
- openEmojiSelector();
|
|
|
- } else {
|
|
|
- addReaction(messageId, emoji);
|
|
|
- }
|
|
|
+ addReaction(messageId, emoji);
|
|
|
};
|
|
|
|
|
|
const handleDeleteMessage = (messageId: number) => {
|
|
|
deleteMessage(
|
|
|
{
|
|
|
+ token,
|
|
|
message_id: messageId,
|
|
|
conversation_with_user: id
|
|
|
},
|
|
|
{
|
|
|
- onSuccess: (res) => {
|
|
|
+ onSuccess: () => {
|
|
|
setMessages((prevMessages) => prevMessages.filter((msg) => msg._id !== messageId));
|
|
|
}
|
|
|
}
|
|
@@ -275,9 +275,11 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
};
|
|
|
|
|
|
const handleOptionPress = (option: string) => {
|
|
|
+ if (!selectedMessage) return;
|
|
|
+
|
|
|
switch (option) {
|
|
|
case 'reply':
|
|
|
- setReplyMessage(selectedMessage?.currentMessage);
|
|
|
+ setReplyMessage(selectedMessage.currentMessage);
|
|
|
setIsModalVisible(false);
|
|
|
break;
|
|
|
case 'copy':
|
|
@@ -286,7 +288,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
Alert.alert('copied');
|
|
|
break;
|
|
|
case 'delete':
|
|
|
- handleDeleteMessage(selectedMessage?.currentMessage?._id);
|
|
|
+ handleDeleteMessage(selectedMessage.currentMessage?._id);
|
|
|
setIsModalVisible(false);
|
|
|
break;
|
|
|
case 'pin':
|
|
@@ -299,6 +301,90 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
closeEmojiSelector();
|
|
|
};
|
|
|
|
|
|
+ const renderTimeContainer = (time: TimeProps<CustomMessage>) => {
|
|
|
+ const createdAt = new Date(time.currentMessage.createdAt);
|
|
|
+
|
|
|
+ const formattedTime = createdAt.toLocaleTimeString([], {
|
|
|
+ hour: '2-digit',
|
|
|
+ minute: '2-digit',
|
|
|
+ hour12: true
|
|
|
+ });
|
|
|
+
|
|
|
+ const hasReactions =
|
|
|
+ time.currentMessage.reactions &&
|
|
|
+ Array.isArray(time.currentMessage.reactions) &&
|
|
|
+ time.currentMessage.reactions.length > 0;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ flexDirection: 'row',
|
|
|
+ justifyContent: hasReactions ? 'space-between' : 'flex-end',
|
|
|
+ alignItems: 'center',
|
|
|
+ paddingHorizontal: 8,
|
|
|
+ paddingBottom: 6,
|
|
|
+ flexShrink: 1,
|
|
|
+ flexGrow: 1,
|
|
|
+ gap: 12
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {hasReactions && (
|
|
|
+ <View
|
|
|
+ style={[
|
|
|
+ {
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ flexShrink: 0,
|
|
|
+ backgroundColor:
|
|
|
+ time.position === 'left' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)',
|
|
|
+ borderRadius: 12,
|
|
|
+ paddingHorizontal: 6,
|
|
|
+ paddingVertical: 4,
|
|
|
+ gap: 6
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ {Object.entries(
|
|
|
+ (Array.isArray(time.currentMessage.reactions)
|
|
|
+ ? time.currentMessage.reactions
|
|
|
+ : []
|
|
|
+ ).reduce((acc: Record<string, number>, { reaction }: { reaction: string }) => {
|
|
|
+ acc[reaction] = (acc[reaction] || 0) + 1;
|
|
|
+ return acc;
|
|
|
+ }, {})
|
|
|
+ ).map(([emoji, count]) => (
|
|
|
+ <Text key={emoji} style={{}}>
|
|
|
+ {emoji}
|
|
|
+ {(count as number) > 1 ? ` ${count}` : ''}
|
|
|
+ </Text>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ <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>
|
|
|
+ {renderTicks(time.currentMessage)}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
const renderSelectedMessage = () =>
|
|
|
selectedMessage && (
|
|
|
<View
|
|
@@ -318,24 +404,11 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
left: { backgroundColor: Colors.FILL_LIGHT }
|
|
|
}}
|
|
|
textStyle={{
|
|
|
- right: { color: Colors.FILL_LIGHT },
|
|
|
+ right: { color: Colors.WHITE },
|
|
|
left: { color: Colors.DARK_BLUE }
|
|
|
}}
|
|
|
- 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.WHITE} />
|
|
|
- </View>
|
|
|
- ) : null;
|
|
|
- }}
|
|
|
+ renderTicks={() => null}
|
|
|
+ renderTime={renderTimeContainer}
|
|
|
/>
|
|
|
</ScrollView>
|
|
|
</View>
|
|
@@ -364,17 +437,19 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
}, [refetch]);
|
|
|
|
|
|
const onSend = useCallback(
|
|
|
- (newMessages: IMessage[] = []) => {
|
|
|
+ (newMessages: CustomMessage[] = []) => {
|
|
|
if (replyMessage) {
|
|
|
newMessages[0].replyMessage = {
|
|
|
text: replyMessage.text,
|
|
|
- id: replyMessage._id
|
|
|
+ id: replyMessage._id,
|
|
|
+ name: replyMessage.user._id === id ? name : 'Me'
|
|
|
};
|
|
|
}
|
|
|
const message = { ...newMessages[0], pending: true };
|
|
|
|
|
|
sendMessage(
|
|
|
{
|
|
|
+ token,
|
|
|
to_uid: id,
|
|
|
text: message.text,
|
|
|
reply_to_id: replyMessage ? (replyMessage._id as number) : -1
|
|
@@ -460,17 +535,19 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
}));
|
|
|
|
|
|
setMessages((previousMessages) =>
|
|
|
- GiftedChat.append(previousMessages, imageMessages as IMessage[])
|
|
|
+ GiftedChat.append(previousMessages, imageMessages as any[])
|
|
|
);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const renderMessageVideo = (props: any) => {
|
|
|
+ const renderMessageVideo = (props: BubbleProps<CustomMessage>) => {
|
|
|
const { currentMessage } = props;
|
|
|
|
|
|
if (currentMessage.video) {
|
|
|
return (
|
|
|
- <LongPressGestureHandler onHandlerStateChange={(event) => handleLongPress(currentMessage)}>
|
|
|
+ <LongPressGestureHandler
|
|
|
+ onHandlerStateChange={(event) => handleLongPress(currentMessage, props)}
|
|
|
+ >
|
|
|
<TouchableOpacity
|
|
|
onPress={() => setSelectedMedia(currentMessage.video)}
|
|
|
style={styles.mediaContainer}
|
|
@@ -489,10 +566,12 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
};
|
|
|
|
|
|
const addReaction = (messageId: number, reaction: string) => {
|
|
|
- const updatedMessages = messages.map((msg) => {
|
|
|
+ const updatedMessages = messages.map((msg: any) => {
|
|
|
if (msg._id === messageId) {
|
|
|
- const updatedReactions = [
|
|
|
- ...(Array.isArray(msg.reactions) ? msg.reactions : []),
|
|
|
+ const updatedReactions: Reaction[] = [
|
|
|
+ ...(Array.isArray(msg.reactions)
|
|
|
+ ? msg.reactions?.filter((r: Reaction) => r.uid !== +currentUserId)
|
|
|
+ : []),
|
|
|
{ datetime: new Date().toISOString(), reaction: reaction, uid: +currentUserId }
|
|
|
];
|
|
|
|
|
@@ -507,7 +586,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
setMessages(updatedMessages);
|
|
|
|
|
|
reactToMessage(
|
|
|
- { message_id: messageId, reaction: reaction, conversation_with_user: id },
|
|
|
+ { token, message_id: messageId, reaction: reaction, conversation_with_user: id },
|
|
|
{
|
|
|
onSuccess: (res) => console.log('res', res),
|
|
|
onError: (err) => console.log('err', err)
|
|
@@ -530,7 +609,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
[replyMessage]
|
|
|
);
|
|
|
|
|
|
- const renderReplyMessageView = (props: BubbleProps<IMessage>) => {
|
|
|
+ const renderReplyMessageView = (props: BubbleProps<CustomMessage>) => {
|
|
|
if (!props.currentMessage) {
|
|
|
return null;
|
|
|
}
|
|
@@ -542,15 +621,40 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
|
|
|
return (
|
|
|
<TouchableOpacity
|
|
|
- style={styles.replyMessageContainer}
|
|
|
+ style={[
|
|
|
+ styles.replyMessageContainer,
|
|
|
+ {
|
|
|
+ backgroundColor:
|
|
|
+ currentMessage.user._id === id ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.2)',
|
|
|
+ borderColor: currentMessage.user._id === id ? Colors.DARK_BLUE : Colors.WHITE
|
|
|
+ }
|
|
|
+ ]}
|
|
|
onPress={() => {
|
|
|
if (currentMessage?.replyMessage?.id) {
|
|
|
scrollToMessage(currentMessage.replyMessage.id);
|
|
|
}
|
|
|
}}
|
|
|
>
|
|
|
- <Text>{currentMessage.replyMessage.text}</Text>
|
|
|
- <View style={styles.replyMessageDivider} />
|
|
|
+ <View style={styles.replyContent}>
|
|
|
+ <Text
|
|
|
+ style={[
|
|
|
+ styles.replyAuthorName,
|
|
|
+ { color: currentMessage.user._id === id ? Colors.DARK_BLUE : Colors.WHITE }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ {currentMessage.replyMessage.name}
|
|
|
+ </Text>
|
|
|
+
|
|
|
+ <Text
|
|
|
+ numberOfLines={1}
|
|
|
+ style={[
|
|
|
+ styles.replyMessageText,
|
|
|
+ { color: currentMessage.user._id === id ? Colors.DARK_BLUE : Colors.WHITE }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ {currentMessage.replyMessage.text}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
</TouchableOpacity>
|
|
|
);
|
|
|
};
|
|
@@ -592,12 +696,11 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
- const renderTicks = (message: IMessage) => {
|
|
|
+ const renderTicks = (message: CustomMessage) => {
|
|
|
if (message.user._id === id) return null;
|
|
|
|
|
|
return message.received ? (
|
|
|
- <View
|
|
|
- >
|
|
|
+ <View>
|
|
|
<MaterialCommunityIcons name="check-all" size={16} color={Colors.WHITE} />
|
|
|
</View>
|
|
|
) : message.sent ? (
|
|
@@ -611,20 +714,27 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
) : null;
|
|
|
};
|
|
|
|
|
|
- const renderBubble = (props: any) => {
|
|
|
+ const renderBubble = (props: BubbleProps<CustomMessage>) => {
|
|
|
const { currentMessage } = props;
|
|
|
|
|
|
if (currentMessage.deleted) {
|
|
|
+ const text = currentMessage.text.length
|
|
|
+ ? props.currentMessage.text
|
|
|
+ : 'This message was deleted';
|
|
|
+
|
|
|
return (
|
|
|
<View>
|
|
|
<Bubble
|
|
|
{...props}
|
|
|
renderTime={() => null}
|
|
|
- currentMessage={{ ...props.currentMessage, text: 'This message was deleted' }}
|
|
|
+ currentMessage={{
|
|
|
+ ...props.currentMessage,
|
|
|
+ text: text
|
|
|
+ }}
|
|
|
renderMessageText={() => (
|
|
|
<View style={{ paddingHorizontal: 12, paddingVertical: 6 }}>
|
|
|
<Text style={{ color: Colors.LIGHT_GRAY, fontStyle: 'italic', fontSize: 12 }}>
|
|
|
- This message was deleted
|
|
|
+ {text}
|
|
|
</Text>
|
|
|
</View>
|
|
|
)}
|
|
@@ -641,7 +751,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
color: Colors.DARK_BLUE
|
|
|
},
|
|
|
right: {
|
|
|
- color: Colors.FILL_LIGHT
|
|
|
+ color: Colors.WHITE
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
@@ -694,88 +804,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
}}
|
|
|
onLongPress={() => handleLongPress(currentMessage, props)}
|
|
|
renderTicks={() => null}
|
|
|
- renderTime={(time) => {
|
|
|
- const createdAt = new Date(time.currentMessage.createdAt);
|
|
|
-
|
|
|
- const formattedTime = createdAt.toLocaleTimeString([], {
|
|
|
- hour: '2-digit',
|
|
|
- minute: '2-digit',
|
|
|
- hour12: true
|
|
|
- });
|
|
|
-
|
|
|
- const hasReactions =
|
|
|
- currentMessage.reactions &&
|
|
|
- Array.isArray(currentMessage.reactions) &&
|
|
|
- currentMessage.reactions.length > 0;
|
|
|
-
|
|
|
- return (
|
|
|
- <View
|
|
|
- style={{
|
|
|
- flexDirection: 'row',
|
|
|
- justifyContent: hasReactions ? 'space-between' : 'flex-end',
|
|
|
- alignItems: 'center',
|
|
|
- paddingHorizontal: 8,
|
|
|
- paddingBottom: 6,
|
|
|
- flexShrink: 1,
|
|
|
- flexGrow: 1,
|
|
|
- gap: 12
|
|
|
- }}
|
|
|
- >
|
|
|
- {hasReactions && (
|
|
|
- <View
|
|
|
- style={[
|
|
|
- {
|
|
|
- flexDirection: 'row',
|
|
|
- alignItems: 'center',
|
|
|
- flexShrink: 0,
|
|
|
- backgroundColor:
|
|
|
- time.position === 'left'
|
|
|
- ? 'rgba(0, 0, 0, 0.2)'
|
|
|
- : 'rgba(255, 255, 255, 0.2)',
|
|
|
- borderRadius: 12,
|
|
|
- paddingHorizontal: 6,
|
|
|
- paddingVertical: 4,
|
|
|
- gap: 6
|
|
|
- }
|
|
|
- ]}
|
|
|
- >
|
|
|
- {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
|
|
|
- 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>
|
|
|
- {renderTicks(currentMessage)}
|
|
|
- </View>
|
|
|
- </View>
|
|
|
- );
|
|
|
- }}
|
|
|
+ renderTime={renderTimeContainer}
|
|
|
/>
|
|
|
</View>
|
|
|
</View>
|
|
@@ -791,11 +820,41 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
// onPressActionButton={openActionSheet}
|
|
|
/>
|
|
|
)}
|
|
|
+ containerStyle={{
|
|
|
+ backgroundColor: Colors.FILL_LIGHT
|
|
|
+ }}
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
+ const renderScrollToBottom = () => {
|
|
|
+ return (
|
|
|
+ <TouchableOpacity
|
|
|
+ style={{
|
|
|
+ position: 'absolute',
|
|
|
+ bottom: -20,
|
|
|
+ right: -20,
|
|
|
+ backgroundColor: Colors.DARK_BLUE,
|
|
|
+ borderRadius: 20,
|
|
|
+ padding: 8
|
|
|
+ }}
|
|
|
+ onPress={() => {
|
|
|
+ if (flatList.current) {
|
|
|
+ flatList.current.scrollToIndex({ index: 0, animated: true });
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <MaterialCommunityIcons name="chevron-down" size={24} color={Colors.WHITE} />
|
|
|
+ </TouchableOpacity>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
- <PageWrapper style={{ marginLeft: 0, marginRight: 0 }}>
|
|
|
+ <SafeAreaView
|
|
|
+ edges={['top']}
|
|
|
+ style={{
|
|
|
+ height: '100%'
|
|
|
+ }}
|
|
|
+ >
|
|
|
<View style={{ paddingHorizontal: '5%' }}>
|
|
|
<Header
|
|
|
label={name}
|
|
@@ -836,7 +895,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
}
|
|
|
}}
|
|
|
renderSystemMessage={renderSystemMessage}
|
|
|
- onSend={(newMessages: IMessage[]) => onSend(newMessages)}
|
|
|
+ onSend={(newMessages: CustomMessage[]) => onSend(newMessages)}
|
|
|
user={{ _id: +currentUserId, name: 'Me' }}
|
|
|
renderBubble={renderBubble}
|
|
|
renderMessageImage={renderMessageImage}
|
|
@@ -844,6 +903,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
renderCustomView={renderReplyMessageView}
|
|
|
isCustomViewBottom={false}
|
|
|
messageContainerRef={messageContainerRef}
|
|
|
+ minComposerHeight={34}
|
|
|
renderSend={(props) => (
|
|
|
<View
|
|
|
style={{
|
|
@@ -883,11 +943,11 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
)}
|
|
|
</View>
|
|
|
)}
|
|
|
- textInputProps={styles.composer}
|
|
|
+ textInputProps={{ ...styles.composer, selectionColor: Colors.LIGHT_GRAY }}
|
|
|
placeholder=""
|
|
|
renderMessage={(props) => (
|
|
|
<ChatMessageBox
|
|
|
- {...props}
|
|
|
+ {...(props as MessageProps<CustomMessage>)}
|
|
|
updateRowRef={updateRowRef}
|
|
|
setReplyOnSwipeOpen={setReplyMessage}
|
|
|
/>
|
|
@@ -895,7 +955,7 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
renderChatFooter={() => (
|
|
|
<ReplyMessageBar clearReply={clearReplyMessage} message={replyMessage} />
|
|
|
)}
|
|
|
- renderMessageVideo={renderMessageVideo}
|
|
|
+ // renderMessageVideo={renderMessageVideo}
|
|
|
renderAvatar={null}
|
|
|
maxComposerHeight={100}
|
|
|
renderComposer={(props) => <Composer {...props} />}
|
|
@@ -903,6 +963,9 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
shouldUpdateMessage={(props, nextProps) =>
|
|
|
highlightedMessageId === nextProps.currentMessage._id
|
|
|
}
|
|
|
+ scrollToBottom={true}
|
|
|
+ scrollToBottomComponent={renderScrollToBottom}
|
|
|
+ scrollToBottomStyle={{ backgroundColor: 'transparent' }}
|
|
|
// inverted={true}
|
|
|
// isTyping={true}
|
|
|
/>
|
|
@@ -966,166 +1029,14 @@ const ChatScreen = ({ route }: { route: any }) => {
|
|
|
</BlurView>
|
|
|
</ReactModal>
|
|
|
</GestureHandlerRootView>
|
|
|
- </PageWrapper>
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ height: insets.bottom,
|
|
|
+ backgroundColor: Colors.FILL_LIGHT
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </SafeAreaView>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-const styles = StyleSheet.create({
|
|
|
- unreadMessagesContainer: {
|
|
|
- padding: 8,
|
|
|
- backgroundColor: Colors.FILL_LIGHT,
|
|
|
- alignItems: 'center'
|
|
|
- },
|
|
|
- unreadMessagesText: {
|
|
|
- color: Colors.DARK_BLUE,
|
|
|
- fontWeight: '700',
|
|
|
- fontSize: getFontSize(12)
|
|
|
- },
|
|
|
- modalBackground: {
|
|
|
- flex: 1
|
|
|
- },
|
|
|
- modalContent: {
|
|
|
- backgroundColor: 'transparent'
|
|
|
- },
|
|
|
- mediaContainer: {
|
|
|
- borderRadius: 10,
|
|
|
- overflow: 'hidden',
|
|
|
- margin: 5
|
|
|
- },
|
|
|
- chatMedia: {
|
|
|
- width: 200,
|
|
|
- height: 200,
|
|
|
- borderRadius: 10
|
|
|
- },
|
|
|
- fullScreenMedia: {
|
|
|
- width: '90%',
|
|
|
- height: '80%'
|
|
|
- },
|
|
|
- audioContainer: {
|
|
|
- flexDirection: 'row',
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'space-between',
|
|
|
- padding: 10,
|
|
|
- backgroundColor: '#f1f1f1',
|
|
|
- borderRadius: 10,
|
|
|
- margin: 5
|
|
|
- },
|
|
|
- progressBar: {
|
|
|
- flexDirection: 'row',
|
|
|
- justifyContent: 'center',
|
|
|
- alignItems: 'center'
|
|
|
- },
|
|
|
- bar: {
|
|
|
- width: 5,
|
|
|
- backgroundColor: 'gray',
|
|
|
- marginHorizontal: 1
|
|
|
- },
|
|
|
- replyMessageContainer: {
|
|
|
- padding: 8,
|
|
|
- paddingBottom: 0
|
|
|
- },
|
|
|
- replyMessageDivider: {
|
|
|
- borderBottomWidth: 1,
|
|
|
- borderBottomColor: 'lightgrey',
|
|
|
- paddingTop: 6
|
|
|
- },
|
|
|
- composer: {
|
|
|
- backgroundColor: Colors.FILL_LIGHT,
|
|
|
- borderRadius: 15,
|
|
|
- borderWidth: 1,
|
|
|
- borderColor: Colors.LIGHT_GRAY,
|
|
|
- paddingHorizontal: 10,
|
|
|
- fontSize: 16,
|
|
|
- marginVertical: 4
|
|
|
- },
|
|
|
- container: {
|
|
|
- flex: 1,
|
|
|
- backgroundColor: 'white'
|
|
|
- },
|
|
|
- imageContainer: {
|
|
|
- borderRadius: 10,
|
|
|
- overflow: 'hidden',
|
|
|
- margin: 5
|
|
|
- },
|
|
|
- chatImage: {
|
|
|
- width: 200,
|
|
|
- height: 200,
|
|
|
- borderRadius: 10
|
|
|
- },
|
|
|
- replyContainer: {
|
|
|
- backgroundColor: '#f1f1f1',
|
|
|
- padding: 5,
|
|
|
- marginBottom: 5,
|
|
|
- borderRadius: 5
|
|
|
- },
|
|
|
- replyText: {
|
|
|
- color: '#333',
|
|
|
- fontStyle: 'italic'
|
|
|
- },
|
|
|
- reactionsContainer: {
|
|
|
- height: 25,
|
|
|
- paddingHorizontal: 6,
|
|
|
- backgroundColor: Colors.FILL_LIGHT,
|
|
|
- bottom: -30,
|
|
|
- position: 'absolute',
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'center',
|
|
|
- flexDirection: 'row',
|
|
|
- gap: 6,
|
|
|
- borderRadius: 15,
|
|
|
- shadowColor: Colors.DARK_BLUE,
|
|
|
- shadowOpacity: 0.2,
|
|
|
- shadowOffset: { width: 0, height: 1 },
|
|
|
- shadowRadius: 2,
|
|
|
- elevation: 1
|
|
|
- },
|
|
|
- reactionText: {
|
|
|
- fontSize: 16,
|
|
|
- marginHorizontal: 5
|
|
|
- },
|
|
|
- reactionsBubble: {
|
|
|
- backgroundColor: '#fff',
|
|
|
- borderRadius: 20,
|
|
|
- padding: 5,
|
|
|
- flexDirection: 'row',
|
|
|
- shadowColor: '#000',
|
|
|
- shadowOpacity: 0.3,
|
|
|
- shadowOffset: { width: 0, height: 2 },
|
|
|
- shadowRadius: 5,
|
|
|
- elevation: 5,
|
|
|
- marginBottom: 10
|
|
|
- },
|
|
|
- replyInputContainer: {
|
|
|
- flexDirection: 'row',
|
|
|
- alignItems: 'center',
|
|
|
- backgroundColor: '#f1f1f1',
|
|
|
- paddingHorizontal: 10,
|
|
|
- paddingVertical: 5,
|
|
|
- borderRadius: 10
|
|
|
- },
|
|
|
- replyInputText: {
|
|
|
- flex: 1,
|
|
|
- color: 'gray'
|
|
|
- },
|
|
|
- modalContainer: {
|
|
|
- flex: 1,
|
|
|
- backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|
|
- justifyContent: 'center',
|
|
|
- alignItems: 'center'
|
|
|
- },
|
|
|
- reactModalContainer: {
|
|
|
- justifyContent: 'flex-end',
|
|
|
- margin: 0
|
|
|
- },
|
|
|
- fullScreenImage: {
|
|
|
- width: '90%',
|
|
|
- height: '80%'
|
|
|
- },
|
|
|
- closeButton: {
|
|
|
- position: 'absolute',
|
|
|
- top: 40,
|
|
|
- right: 20
|
|
|
- }
|
|
|
-});
|
|
|
-
|
|
|
export default ChatScreen;
|