Skip to content

Commit d6b7734

Browse files
committed
Implement message reply info.
1 parent abe92cc commit d6b7734

File tree

9 files changed

+289
-212
lines changed

9 files changed

+289
-212
lines changed

packages/uikit-react-native-foundation/src/ui/GroupChannelMessage/MessageContainer.tsx

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const MessageContainer = (props: Props) => {
2323

2424
MessageContainer.Incoming = function MessageContainerIncoming({
2525
children,
26+
replyInfo,
2627
groupedWithNext,
2728
groupedWithPrev,
2829
message,
@@ -34,43 +35,48 @@ MessageContainer.Incoming = function MessageContainerIncoming({
3435
const color = colors.ui.groupChannelMessage.incoming;
3536

3637
return (
37-
<Box flexDirection={'row'} justifyContent={'flex-start'} alignItems={'flex-end'}>
38-
<Box width={26} marginRight={12}>
39-
{(message.isFileMessage() || message.isUserMessage()) && !groupedWithNext && (
40-
<Pressable onPress={onPressAvatar}>
41-
<Avatar size={26} uri={message.sender?.profileUrl} />
42-
</Pressable>
43-
)}
44-
</Box>
45-
<Box flexShrink={1}>
46-
{parentMessage}
47-
{!groupedWithPrev && !message.parentMessage && (
48-
<Box marginLeft={12} marginBottom={4}>
49-
{(message.isFileMessage() || message.isUserMessage()) && (
50-
<Text caption1 color={color.enabled.textSenderName} numberOfLines={1}>
51-
{strings?.senderName ?? message.sender.nickname}
52-
</Text>
53-
)}
54-
</Box>
55-
)}
56-
57-
<Box flexDirection={'row'} alignItems={'flex-end'}>
58-
<Box style={styles.bubble}>{children}</Box>
59-
{!groupedWithNext && (
60-
<Box marginLeft={4}>
61-
<Text caption4 color={color.enabled.textTime}>
62-
{strings?.sentDate ?? getMessageTimeFormat(new Date(message.createdAt))}
63-
</Text>
38+
<Box flexDirection={'column'} justifyContent={'flex-start'} alignItems={'flex-start'}>
39+
<Box flexDirection={'row'} justifyContent={'flex-start'} alignItems={'flex-end'}>
40+
<Box width={26} marginRight={12}>
41+
{(message.isFileMessage() || message.isUserMessage()) && !groupedWithNext && (
42+
<Pressable onPress={onPressAvatar}>
43+
<Avatar size={26} uri={message.sender?.profileUrl} />
44+
</Pressable>
45+
)}
46+
</Box>
47+
<Box flexShrink={1}>
48+
{parentMessage}
49+
{!groupedWithPrev && !message.parentMessage && (
50+
<Box marginLeft={12} marginBottom={4}>
51+
{(message.isFileMessage() || message.isUserMessage()) && (
52+
<Text caption1 color={color.enabled.textSenderName} numberOfLines={1}>
53+
{strings?.senderName ?? message.sender.nickname}
54+
</Text>
55+
)}
6456
</Box>
6557
)}
58+
<Box flexDirection={'row'} alignItems={'flex-end'}>
59+
<Box style={styles.bubble}>{children}</Box>
60+
{!groupedWithNext && (
61+
<Box marginLeft={4}>
62+
<Text caption4 color={color.enabled.textTime}>
63+
{strings?.sentDate ?? getMessageTimeFormat(new Date(message.createdAt))}
64+
</Text>
65+
</Box>
66+
)}
67+
</Box>
6668
</Box>
6769
</Box>
70+
<Box marginLeft={40} marginTop={4} flexDirection={'row'} height={20}>
71+
{replyInfo}
72+
</Box>
6873
</Box>
6974
);
7075
};
7176

7277
MessageContainer.Outgoing = function MessageContainerOutgoing({
7378
children,
79+
replyInfo,
7480
message,
7581
groupedWithNext,
7682
strings,
@@ -96,6 +102,9 @@ MessageContainer.Outgoing = function MessageContainerOutgoing({
96102
</Box>
97103
<Box style={styles.bubble}>{children}</Box>
98104
</Box>
105+
<Box marginTop={4} flexDirection={'row-reverse'} height={20}>
106+
{replyInfo}
107+
</Box>
99108
</Box>
100109
);
101110
};

packages/uikit-react-native-foundation/src/ui/GroupChannelMessage/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type GroupChannelMessageProps<T extends SendbirdMessage, AdditionalProps
2828
children?: React.ReactNode;
2929
sendingStatus?: React.ReactNode;
3030
parentMessage?: React.ReactNode;
31+
replyInfo?: React.ReactNode;
3132

3233
groupedWithPrev: boolean;
3334
groupedWithNext: boolean;

packages/uikit-react-native/src/components/ChannelMessageList/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
7171
onPress?: () => void;
7272
onLongPress?: () => void;
7373
onPressParentMessage?: ChannelMessageListProps<T>['onPressParentMessage'];
74+
onReplyInThreadMessage?: ChannelMessageListProps<T>['onReplyInThreadMessage'];
7475
onShowUserProfile?: UserProfileContextType['show'];
7576
channel: T;
7677
currentUserId?: ChannelMessageListProps<T>['currentUserId'];
@@ -143,6 +144,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
143144
onPress,
144145
onLongPress,
145146
onPressParentMessage,
147+
onReplyInThreadMessage,
146148
onShowUserProfile: show,
147149
enableMessageGrouping,
148150
channel,
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React from 'react';
2+
import { User } from '@sendbird/chat';
3+
import { Avatar, Box, createStyleSheet, Icon, PressBox, Text, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
4+
import { SendbirdFileMessage, SendbirdGroupChannel, SendbirdMessage, SendbirdUserMessage } from '@sendbird/uikit-utils';
5+
6+
import { useLocalization } from '../../hooks/useContext';
7+
8+
const AVATAR_LIMIT = 5;
9+
10+
type Props = {
11+
channel: SendbirdGroupChannel;
12+
message: SendbirdMessage;
13+
onPress?: (message: SendbirdUserMessage | SendbirdFileMessage) => void;
14+
};
15+
16+
const createRepliedUserAvatars = (mostRepliedUsers: User[]) => {
17+
if (!mostRepliedUsers || mostRepliedUsers.length === 0) return null;
18+
19+
const { palette } = useUIKitTheme();
20+
21+
return mostRepliedUsers.slice(0, AVATAR_LIMIT).map((user, index) => {
22+
if (index < AVATAR_LIMIT - 1) {
23+
return <Box style={styles.avatarContainer} key={index}>
24+
<Avatar size={20} uri={user?.profileUrl} containerStyle={styles.avatar}></Avatar>
25+
</Box>;
26+
} else {
27+
return <Box style={styles.avatarContainer} key={index}>
28+
<Avatar size={20} uri={user?.profileUrl} containerStyle={styles.avatar}></Avatar>
29+
<Box style={styles.avatarOverlay} backgroundColor={palette.overlay01}>
30+
<Icon icon={'plus'} size={14} style={styles.plusIcon} color={'white'} />
31+
</Box>
32+
</Box>;
33+
}
34+
});
35+
};
36+
37+
const GroupChannelMessageReplyInfo = ({ channel, message, onPress }: Props) => {
38+
const { STRINGS } = useLocalization();
39+
const { select, palette } = useUIKitTheme();
40+
41+
if (!channel || !message.threadInfo || !message.threadInfo.replyCount) return null;
42+
43+
const replyCountText = STRINGS.GROUP_CHANNEL_THREAD.REPLAY_POSTFIX(message.threadInfo.replyCount || 0);
44+
const onPressReply = () => {
45+
onPress?.(message as SendbirdUserMessage | SendbirdFileMessage);
46+
};
47+
48+
const renderAvatars = createRepliedUserAvatars(message.threadInfo.mostRepliedUsers);
49+
50+
return <PressBox onPress={onPressReply} style={styles.messageContainer}>
51+
{renderAvatars}
52+
<Text caption3 color={select({ light: palette.primary300, dark: palette.primary200 })} style={styles.message}>
53+
{replyCountText}
54+
</Text>
55+
</PressBox>;
56+
};
57+
58+
const styles = createStyleSheet({
59+
container: {
60+
flexDirection: 'row',
61+
},
62+
messageContainer: {
63+
flexDirection: 'row',
64+
alignItems: 'flex-end',
65+
},
66+
message: {
67+
marginHorizontal: 4,
68+
},
69+
avatarContainer: {
70+
marginRight: 4,
71+
width: 20,
72+
height: 20,
73+
},
74+
avatar: {
75+
width: '100%',
76+
height: '100%',
77+
},
78+
avatarOverlay: {
79+
position: 'absolute',
80+
top: 0,
81+
left: 0,
82+
right: 0,
83+
bottom: 0,
84+
borderRadius: 10,
85+
},
86+
plusIcon: {
87+
position: 'absolute',
88+
top: 3,
89+
left: 3,
90+
right: 0,
91+
bottom: 0,
92+
},
93+
});
94+
95+
export default React.memo(GroupChannelMessageReplyInfo);

packages/uikit-react-native/src/components/GroupChannelMessageRenderer/index.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import GroupChannelMessageDateSeparator from './GroupChannelMessageDateSeparator
3333
import GroupChannelMessageFocusAnimation from './GroupChannelMessageFocusAnimation';
3434
import GroupChannelMessageOutgoingStatus from './GroupChannelMessageOutgoingStatus';
3535
import GroupChannelMessageParentMessage from './GroupChannelMessageParentMessage';
36+
import GroupChannelMessageReplyInfo from './GroupChannelMessageReplyInfo';
3637

3738
const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'] = ({
3839
channel,
@@ -41,6 +42,7 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
4142
onLongPress,
4243
onPressParentMessage,
4344
onShowUserProfile,
45+
onReplyInThreadMessage,
4446
enableMessageGrouping,
4547
focused,
4648
prevMessage,
@@ -58,7 +60,8 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
5860
prevMessage,
5961
nextMessage,
6062
);
61-
63+
const variant = isMyMessage(message, currentUser?.userId) ? 'outgoing' : 'incoming';
64+
6265
const reactionChildren = useIIFE(() => {
6366
const configs = sbOptions.uikitWithAppInfo.groupChannel.channel;
6467
if (
@@ -70,7 +73,13 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
7073
}
7174
return null;
7275
});
73-
76+
77+
const renderReplyInfo = useIIFE(() => {
78+
if (sbOptions.uikit.groupChannel.channel.replyType !== 'thread') return null;
79+
if (!channel || !message.threadInfo || !message.threadInfo.replyCount) return null;
80+
return <GroupChannelMessageReplyInfo channel={channel} message={message} onPress={onReplyInThreadMessage} />;
81+
});
82+
7483
const resetPlayer = async () => {
7584
playerUnsubscribes.current.forEach((unsubscribe) => {
7685
try {
@@ -81,8 +90,6 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
8190
await playerService.reset();
8291
};
8392

84-
const variant = isMyMessage(message, currentUser?.userId) ? 'outgoing' : 'incoming';
85-
8693
const messageProps: Omit<GroupChannelMessageProps<SendbirdMessage>, 'message'> = {
8794
channel,
8895
variant,
@@ -147,6 +154,7 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
147154
groupedWithPrev: groupWithPrev,
148155
groupedWithNext: groupWithNext,
149156
children: reactionChildren,
157+
replyInfo: renderReplyInfo,
150158
sendingStatus: isMyMessage(message, currentUser?.userId) ? (
151159
<GroupChannelMessageOutgoingStatus channel={channel} message={message} />
152160
) : null,

packages/uikit-react-native/src/domain/groupChannelThread/component/GroupChannelThreadMessageList.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import React, { useContext, useEffect } from 'react';
2-
31
import { useChannelHandler } from '@sendbird/uikit-chat-hooks';
42
import { isDifferentChannel, useFreshCallback, useUniqHandlerId } from '@sendbird/uikit-utils';
3+
import React, { useContext, useEffect, useLayoutEffect } from 'react';
54

65
import ChannelMessageList from '../../../components/ChannelMessageList';
76
import { useSendbirdChat } from '../../../hooks/useContext';
@@ -12,7 +11,7 @@ const GroupChannelThreadMessageList = (props: GroupChannelThreadProps['MessageLi
1211
const { sdk } = useSendbirdChat();
1312
const { setMessageToEdit } = useContext(GroupChannelThreadContexts.Fragment);
1413
const { subscribe } = useContext(GroupChannelThreadContexts.PubSub);
15-
const { flatListRef, lazyScrollToBottom } = useContext(GroupChannelThreadContexts.MessageList);
14+
const { flatListRef, lazyScrollToBottom, lazyScrollToIndex } = useContext(GroupChannelThreadContexts.MessageList);
1615

1716
const id = useUniqHandlerId('GroupChannelThreadMessageList');
1817

@@ -28,6 +27,16 @@ const GroupChannelThreadMessageList = (props: GroupChannelThreadProps['MessageLi
2827
}
2928
});
3029

30+
useLayoutEffect(() => {
31+
if (props.startingPoint) {
32+
const foundMessageIndex = props.messages.findIndex((it) => it.createdAt === props.startingPoint);
33+
const isIncludedInList = foundMessageIndex > -1;
34+
if (isIncludedInList) {
35+
lazyScrollToIndex({ index: foundMessageIndex, animated: true, timeout: 100 });
36+
}
37+
}
38+
}, [props.startingPoint]);
39+
3140
useChannelHandler(sdk, id, {
3241
onReactionUpdated(channel, event) {
3342
if (isDifferentChannel(channel, props.channel)) return;

0 commit comments

Comments
 (0)