Skip to content

Commit

Permalink
Implement GroupChannel thread.
Browse files Browse the repository at this point in the history
  • Loading branch information
OnestarLee committed May 30, 2024
1 parent 14c23fb commit 1727b18
Show file tree
Hide file tree
Showing 48 changed files with 3,392 additions and 70 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const IconAssets = {
'streaming': require('./icon-streaming.png'),
'supergroup': require('./icon-supergroup.png'),
'theme': require('./icon-theme.png'),
'thread': require('./icon-thread.png'),
'thumbnail-none': require('./icon-thumbnail-none.png'),
'unarchive': require('./icon-unarchive.png'),
'user': require('./icon-user.png'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const MessageContainer = (props: Props) => {

MessageContainer.Incoming = function MessageContainerIncoming({
children,
replyInfo,
groupedWithNext,
groupedWithPrev,
message,
Expand All @@ -34,43 +35,48 @@ MessageContainer.Incoming = function MessageContainerIncoming({
const color = colors.ui.groupChannelMessage.incoming;

return (
<Box flexDirection={'row'} justifyContent={'flex-start'} alignItems={'flex-end'}>
<Box width={26} marginRight={12}>
{(message.isFileMessage() || message.isUserMessage()) && !groupedWithNext && (
<Pressable onPress={onPressAvatar}>
<Avatar size={26} uri={message.sender?.profileUrl} />
</Pressable>
)}
</Box>
<Box flexShrink={1}>
{parentMessage}
{!groupedWithPrev && !message.parentMessage && (
<Box marginLeft={12} marginBottom={4}>
{(message.isFileMessage() || message.isUserMessage()) && (
<Text caption1 color={color.enabled.textSenderName} numberOfLines={1}>
{strings?.senderName ?? message.sender.nickname}
</Text>
)}
</Box>
)}

<Box flexDirection={'row'} alignItems={'flex-end'}>
<Box style={styles.bubble}>{children}</Box>
{!groupedWithNext && (
<Box marginLeft={4}>
<Text caption4 color={color.enabled.textTime}>
{strings?.sentDate ?? getMessageTimeFormat(new Date(message.createdAt))}
</Text>
<Box flexDirection={'column'} justifyContent={'flex-start'} alignItems={'flex-start'}>
<Box flexDirection={'row'} justifyContent={'flex-start'} alignItems={'flex-end'}>
<Box width={26} marginRight={12}>
{(message.isFileMessage() || message.isUserMessage()) && !groupedWithNext && (
<Pressable onPress={onPressAvatar}>
<Avatar size={26} uri={message.sender?.profileUrl} />
</Pressable>
)}
</Box>
<Box flexShrink={1}>
{parentMessage}
{!groupedWithPrev && !message.parentMessage && (
<Box marginLeft={12} marginBottom={4}>
{(message.isFileMessage() || message.isUserMessage()) && (
<Text caption1 color={color.enabled.textSenderName} numberOfLines={1}>
{strings?.senderName ?? message.sender.nickname}
</Text>
)}
</Box>
)}
<Box flexDirection={'row'} alignItems={'flex-end'}>
<Box style={styles.bubble}>{children}</Box>
{!groupedWithNext && (
<Box marginLeft={4}>
<Text caption4 color={color.enabled.textTime}>
{strings?.sentDate ?? getMessageTimeFormat(new Date(message.createdAt))}
</Text>
</Box>
)}
</Box>
</Box>
</Box>
<Box marginLeft={40} marginTop={4} flexDirection={'row'} height={20}>
{replyInfo}
</Box>
</Box>
);
};

MessageContainer.Outgoing = function MessageContainerOutgoing({
children,
replyInfo,
message,
groupedWithNext,
strings,
Expand All @@ -96,6 +102,9 @@ MessageContainer.Outgoing = function MessageContainerOutgoing({
</Box>
<Box style={styles.bubble}>{children}</Box>
</Box>
<Box marginTop={4} flexDirection={'row-reverse'} height={20}>
{replyInfo}
</Box>
</Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type GroupChannelMessageProps<T extends SendbirdMessage, AdditionalProps
children?: React.ReactNode;
sendingStatus?: React.ReactNode;
parentMessage?: React.ReactNode;
replyInfo?: React.ReactNode;

groupedWithPrev: boolean;
groupedWithNext: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
channel,
messageToReply,
setMessageToReply,
messageToThread,
},
ref,
) {
Expand All @@ -68,16 +69,21 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
visible: voiceMessageInputVisible,
setVisible: setVoiceMessageInputVisible,
} = useDeferredModalState();

const messageReplyParams = useIIFE(() => {
const { groupChannel } = sbOptions.uikit;
if (!channel.isGroupChannel() || groupChannel.channel.replyType === 'none' || !messageToReply) return {};
if (!channel.isGroupChannel() || groupChannel.channel.replyType === 'none'
|| (groupChannel.channel.replyType === 'quote_reply' && !messageToReply)
|| (groupChannel.channel.replyType === 'thread' && !messageToThread)) {
return {};
}

return {
parentMessageId: messageToReply.messageId,
parentMessageId: messageToReply?.messageId ?? messageToThread?.messageId,
isReplyToChannel: true,
};
});

const messageMentionParams = useIIFE(() => {
const { groupChannel } = sbOptions.uikit;
if (!channel.isGroupChannel() || !groupChannel.channel.enableMention) return {};
Expand Down Expand Up @@ -152,6 +158,7 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
if (inputFrozen) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_DISABLED;
if (inputDisabled) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_DISABLED;
if (messageToReply) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_REPLY;
if (messageToThread) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_THREAD;

return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_ACTIVE;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export type ChannelInputProps = {
// reply - only available on group channel
messageToReply?: undefined | SendbirdUserMessage | SendbirdFileMessage;
setMessageToReply?: (message?: undefined | SendbirdUserMessage | SendbirdFileMessage) => void;
messageToThread?: undefined | SendbirdUserMessage | SendbirdFileMessage;

// mention
SuggestedMentionList?: CommonComponent<SuggestedMentionListProps>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe

onEditMessage: (message: HandleableMessage) => void;
onReplyMessage?: (message: HandleableMessage) => void; // only available on group channel
onReplyInThreadMessage?: (message: HandleableMessage) => void; // only available on group channel
onDeleteMessage: (message: HandleableMessage) => Promise<void>;
onResendFailedMessage: (failedMessage: HandleableMessage) => Promise<HandleableMessage | void>;
onPressParentMessage?: (parentMessage: SendbirdMessage) => void;
onPressParentMessage?: (parentMessage: SendbirdMessage, childMessage: HandleableMessage) => void;
onPressMediaMessage?: (message: SendbirdFileMessage, deleteMessage: () => Promise<void>, uri: string) => void;

renderMessage: (props: {
Expand All @@ -70,12 +71,14 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
onPress?: () => void;
onLongPress?: () => void;
onPressParentMessage?: ChannelMessageListProps<T>['onPressParentMessage'];
onReplyInThreadMessage?: ChannelMessageListProps<T>['onReplyInThreadMessage'];
onShowUserProfile?: UserProfileContextType['show'];
channel: T;
currentUserId?: ChannelMessageListProps<T>['currentUserId'];
enableMessageGrouping: ChannelMessageListProps<T>['enableMessageGrouping'];
bottomSheetItem?: BottomSheetItem;
isFirstItem: boolean;
hideParentMessage?: boolean;
}) => React.ReactElement | null;
renderNewMessagesButton:
| null
Expand All @@ -93,6 +96,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
channel,
onEditMessage,
onReplyMessage,
onReplyInThreadMessage,
onDeleteMessage,
onResendFailedMessage,
onPressMediaMessage,
Expand Down Expand Up @@ -123,6 +127,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
currentUserId,
onEditMessage,
onReplyMessage,
onReplyInThreadMessage,
onDeleteMessage,
onResendFailedMessage,
onPressMediaMessage,
Expand All @@ -139,6 +144,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
onPress,
onLongPress,
onPressParentMessage,
onReplyInThreadMessage,
onShowUserProfile: show,
enableMessageGrouping,
channel,
Expand Down Expand Up @@ -196,6 +202,7 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
onResendFailedMessage,
onEditMessage,
onReplyMessage,
onReplyInThreadMessage,
onDeleteMessage,
onPressMediaMessage,
}: Pick<
Expand All @@ -204,6 +211,7 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
| 'currentUserId'
| 'onEditMessage'
| 'onReplyMessage'
| 'onReplyInThreadMessage'
| 'onDeleteMessage'
| 'onResendFailedMessage'
| 'onPressMediaMessage'
Expand Down Expand Up @@ -322,6 +330,12 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
title: STRINGS.LABELS.CHANNEL_MESSAGE_REPLY,
onPress: () => onReplyMessage?.(message),
}),
replyInThread: (message: HandleableMessage) => ({
disabled: Boolean(message.parentMessageId),
icon: 'thread' as const,
title: STRINGS.LABELS.CHANNEL_MESSAGE_THREAD,
onPress: () => onReplyInThreadMessage?.(message),
}),
download: (message: HandleableMessage) => ({
icon: 'download' as const,
title: STRINGS.LABELS.CHANNEL_MESSAGE_SAVE,
Expand All @@ -336,8 +350,12 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
sheetItems.push(menu.edit(message));
sheetItems.push(menu.delete(message));
}
if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
sheetItems.push(menu.reply(message));
if (channel.isGroupChannel()) {
if (sbOptions.uikit.groupChannel.channel.replyType === 'thread' && onReplyInThreadMessage !== undefined) {
sheetItems.push(menu.replyInThread(message));
} else if (sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
sheetItems.push(menu.reply(message));
}
}
}
}
Expand All @@ -350,8 +368,14 @@ const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdO
if (isMyMessage(message, currentUserId) && message.sendingStatus === 'succeeded') {
sheetItems.push(menu.delete(message));
}
if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
sheetItems.push(menu.reply(message));
if (channel.isGroupChannel()) {
if (channel.isGroupChannel()) {
if (sbOptions.uikit.groupChannel.channel.replyType === 'thread' && onReplyInThreadMessage !== undefined) {
sheetItems.push(menu.replyInThread(message));
} else if (sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
sheetItems.push(menu.reply(message));
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Props = {
channel: SendbirdGroupChannel;
message: SendbirdUserMessage | SendbirdFileMessage;
childMessage: SendbirdUserMessage | SendbirdFileMessage;
onPress?: (message: SendbirdMessage) => void;
onPress?: (parentMessage: SendbirdMessage, childMessage: SendbirdUserMessage | SendbirdFileMessage) => void;
};

const GroupChannelMessageParentMessage = ({ variant, channel, message, childMessage, onPress }: Props) => {
Expand Down Expand Up @@ -135,7 +135,7 @@ const GroupChannelMessageParentMessage = ({ variant, channel, message, childMess
paddingLeft={variant === 'outgoing' ? 0 : 12}
paddingRight={variant === 'outgoing' ? 12 : 0}
>
<PressBox onPress={() => onPress?.(parentMessage)} style={styles.senderLabel}>
<PressBox onPress={() => onPress?.(parentMessage, childMessage)} style={styles.senderLabel}>
<Icon icon={'reply-filled'} size={13} color={colors.onBackground03} containerStyle={{ marginRight: 4 }} />
<Text caption1 color={colors.onBackground03}>
{STRINGS.LABELS.REPLY_FROM_SENDER_TO_RECEIVER(childMessage, parentMessage, currentUser?.userId)}
Expand All @@ -147,7 +147,7 @@ const GroupChannelMessageParentMessage = ({ variant, channel, message, childMess
justifyContent={variant === 'outgoing' ? 'flex-end' : 'flex-start'}
style={styles.messageContainer}
>
<PressBox onPress={() => onPress?.(parentMessage)}>{parentMessageComponent}</PressBox>
<PressBox onPress={() => onPress?.(parentMessage, childMessage)}>{parentMessageComponent}</PressBox>
</Box>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { User } from '@sendbird/chat';
import { Avatar, Box, createStyleSheet, Icon, PressBox, Text, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
import { SendbirdFileMessage, SendbirdGroupChannel, SendbirdMessage, SendbirdUserMessage } from '@sendbird/uikit-utils';

import { useLocalization } from '../../hooks/useContext';

const AVATAR_LIMIT = 5;

type Props = {
channel: SendbirdGroupChannel;
message: SendbirdMessage;
onPress?: (message: SendbirdUserMessage | SendbirdFileMessage) => void;
};

const createRepliedUserAvatars = (mostRepliedUsers: User[]) => {
if (!mostRepliedUsers || mostRepliedUsers.length === 0) return null;

const { palette } = useUIKitTheme();

return mostRepliedUsers.slice(0, AVATAR_LIMIT).map((user, index) => {
if (index < AVATAR_LIMIT - 1) {
return <Box style={styles.avatarContainer} key={index}>
<Avatar size={20} uri={user?.profileUrl} containerStyle={styles.avatar}></Avatar>
</Box>;
} else {
return <Box style={styles.avatarContainer} key={index}>
<Avatar size={20} uri={user?.profileUrl} containerStyle={styles.avatar}></Avatar>
<Box style={styles.avatarOverlay} backgroundColor={palette.overlay01}>
<Icon icon={'plus'} size={14} style={styles.plusIcon} color={'white'} />
</Box>
</Box>;
}
});
};

const GroupChannelMessageReplyInfo = ({ channel, message, onPress }: Props) => {
const { STRINGS } = useLocalization();
const { select, palette } = useUIKitTheme();

if (!channel || !message.threadInfo || !message.threadInfo.replyCount) return null;

const replyCountText = STRINGS.GROUP_CHANNEL_THREAD.REPLAY_POSTFIX(message.threadInfo.replyCount || 0);
const onPressReply = () => {
onPress?.(message as SendbirdUserMessage | SendbirdFileMessage);
};

const renderAvatars = createRepliedUserAvatars(message.threadInfo.mostRepliedUsers);

return <PressBox onPress={onPressReply} style={styles.messageContainer}>
{renderAvatars}
<Text caption3 color={select({ light: palette.primary300, dark: palette.primary200 })} style={styles.message}>
{replyCountText}
</Text>
</PressBox>;
};

const styles = createStyleSheet({
container: {
flexDirection: 'row',
},
messageContainer: {
flexDirection: 'row',
alignItems: 'flex-end',
},
message: {
marginHorizontal: 4,
},
avatarContainer: {
marginRight: 4,
width: 20,
height: 20,
},
avatar: {
width: '100%',
height: '100%',
},
avatarOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
borderRadius: 10,
},
plusIcon: {
position: 'absolute',
top: 3,
left: 3,
right: 0,
bottom: 0,
},
});

export default React.memo(GroupChannelMessageReplyInfo);
Loading

0 comments on commit 1727b18

Please sign in to comment.