Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(UIKIT-4240): implement basic quote reply logic #103

Merged
merged 4 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ function isNotEmpty(arr?: unknown[]): arr is unknown[] {
return arr.length !== 0;
}

function shouldUseSearchLimit(startingPoint: number) {
return startingPoint < Date.now();
}

export const useGroupChannelMessagesWithCollection: UseGroupChannelMessages = (sdk, channel, userId, options) => {
const initialStartingPoint = options?.startingPoint ?? Number.MAX_SAFE_INTEGER;
const initialLimit = typeof options?.startingPoint === 'number' ? MESSAGE_LIMIT.SEARCH : MESSAGE_LIMIT.DEFAULT;
const initialLimit = shouldUseSearchLimit(initialStartingPoint) ? MESSAGE_LIMIT.SEARCH : MESSAGE_LIMIT.DEFAULT;

const forceUpdate = useForceUpdate();
const collectionRef = useRef<SendbirdMessageCollection>();
Expand Down Expand Up @@ -347,9 +351,10 @@ export const useGroupChannelMessagesWithCollection: UseGroupChannelMessages = (s
});
const resetWithStartingPoint: ReturnType<UseGroupChannelMessages>['resetWithStartingPoint'] = useFreshCallback(
(startingPoint, callback) => {
const limit = shouldUseSearchLimit(startingPoint) ? MESSAGE_LIMIT.SEARCH : MESSAGE_LIMIT.DEFAULT;
updateLoading(true);
updateMessages([], true, sdk.currentUser.userId);
init(startingPoint, MESSAGE_LIMIT.DEFAULT, () => {
init(startingPoint, limit, () => {
updateLoading(false);
callback?.();
});
Expand Down
151 changes: 104 additions & 47 deletions packages/uikit-react-native/src/components/ChannelInput/SendInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import {
} from 'react-native';

import { MentionType } from '@sendbird/chat/message';
import type { BottomSheetItem } from '@sendbird/uikit-react-native-foundation';
import {
Icon,
Image,
PressBox,
Text,
TextInput,
createStyleSheet,
useAlert,
useBottomSheet,
useToast,
useUIKitTheme,
} from '@sendbird/uikit-react-native-foundation';
import type { BottomSheetItem } from '@sendbird/uikit-react-native-foundation';
import { SendbirdChannel, isImage, shouldCompressImage, useIIFE } from '@sendbird/uikit-utils';
import { Logger, SendbirdChannel, isImage, shouldCompressImage, useIIFE } from '@sendbird/uikit-utils';

import { useLocalization, usePlatformService, useSendbirdChat } from '../../hooks/useContext';
import SBUError from '../../libs/SBUError';
Expand Down Expand Up @@ -48,6 +51,8 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
inputFrozen,
inputMuted,
channel,
messageToReply,
setMessageToReply,
},
ref,
) {
Expand All @@ -57,30 +62,55 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
const { openSheet } = useBottomSheet();
const toast = useToast();

const onFailureToSend = () => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error');
const messageReplyParams = useIIFE(() => {
const { groupChannel } = sbOptions.uikit;
if (!channel.isGroupChannel() || groupChannel.channel.replyType === 'none' || !messageToReply) return {};
return {
parentMessageId: messageToReply.messageId,
isReplyToChannel: true,
};
});

const sendUserMessage = () => {
const mentionType = MentionType.USERS;
const mentionedUserIds = mentionedUsers.map((it) => it.user.userId);
const mentionedMessageTemplate = mentionManager.textToMentionedMessageTemplate(
text,
mentionedUsers,
sbOptions.uikit.groupChannel.channel.enableMention,
);
const messageMentionParams = useIIFE(() => {
const { groupChannel } = sbOptions.uikit;
if (!channel.isGroupChannel() || !groupChannel.channel.enableMention) return {};
return {
mentionType: MentionType.USERS,
mentionedUserIds: mentionedUsers.map((it) => it.user.userId),
mentionedMessageTemplate: mentionManager.textToMentionedMessageTemplate(
text,
mentionedUsers,
groupChannel.channel.enableMention,
),
};
});

const onFailureToSend = (error: Error) => {
toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error');
Logger.error(STRINGS.TOAST.SEND_MSG_ERROR, error);
};

const sendUserMessage = () => {
onPressSendUserMessage({
message: text,
mentionType,
mentionedUserIds,
mentionedMessageTemplate,
...messageMentionParams,
...messageReplyParams,
}).catch(onFailureToSend);

onChangeText('');
setMessageToReply?.();
};

const sheetItems = useChannelInputItems(channel, (file) => {
onPressSendFileMessage({ file }).catch(onFailureToSend);
});
const sendFileMessage = (file: FileType) => {
onPressSendFileMessage({
file,
...messageReplyParams,
}).catch(onFailureToSend);

setMessageToReply?.();
};

const sheetItems = useChannelInputItems(channel, sendFileMessage);
const onPressAttachment = () => openSheet({ sheetItems });

const getPlaceholder = () => {
Expand All @@ -92,37 +122,64 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
};

return (
<View style={styles.sendInputContainer}>
{AttachmentsButton && <AttachmentsButton onPress={onPressAttachment} disabled={inputDisabled} />}
<TextInput
ref={ref}
multiline
disableFullscreenUI
onSelectionChange={onSelectionChange}
editable={!inputDisabled}
onChangeText={onChangeText}
style={styles.input}
placeholder={getPlaceholder()}
>
{mentionManager.textToMentionedComponents(
text,
mentionedUsers,
sbOptions.uikit.groupChannel.channel.enableMention,
)}
</TextInput>

{Boolean(text.trim()) && (
<TouchableOpacity onPress={sendUserMessage} disabled={inputDisabled}>
<Icon
color={
inputDisabled ? colors.ui.input.default.disabled.highlight : colors.ui.input.default.active.highlight
}
icon={'send'}
size={24}
containerStyle={styles.iconSend}
/>
</TouchableOpacity>
<View>
{/** TODO: Reply message component */}
{messageToReply && (
<View
style={{
flexDirection: 'row',
paddingLeft: 18,
paddingRight: 16,
paddingVertical: 12,
alignItems: 'center',
borderTopWidth: 1,
borderColor: colors.onBackground04,
}}
>
<View style={{ borderWidth: 1, flex: 1, height: 32 }}>
{messageToReply.isFileMessage() ? (
<Image style={{ width: 30, height: 30 }} source={{ uri: messageToReply.url }} />
) : (
<Text>{messageToReply.message}</Text>
)}
</View>
<PressBox onPress={() => setMessageToReply?.(undefined)} style={{ borderWidth: 1, marginLeft: 16 }}>
<Icon icon={'close'} size={24} color={colors.onBackground01} />
</PressBox>
</View>
)}
<View style={styles.sendInputContainer}>
{AttachmentsButton && <AttachmentsButton onPress={onPressAttachment} disabled={inputDisabled} />}
<TextInput
ref={ref}
multiline
disableFullscreenUI
onSelectionChange={onSelectionChange}
editable={!inputDisabled}
onChangeText={onChangeText}
style={styles.input}
placeholder={getPlaceholder()}
>
{mentionManager.textToMentionedComponents(
text,
mentionedUsers,
sbOptions.uikit.groupChannel.channel.enableMention,
)}
</TextInput>

{Boolean(text.trim()) && (
<TouchableOpacity onPress={sendUserMessage} disabled={inputDisabled}>
<Icon
color={
inputDisabled ? colors.ui.input.default.disabled.highlight : colors.ui.input.default.active.highlight
}
icon={'send'}
size={24}
containerStyle={styles.iconSend}
/>
</TouchableOpacity>
)}
</View>
</View>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export type ChannelInputProps = {
messageToEdit: undefined | SendbirdUserMessage | SendbirdFileMessage;
setMessageToEdit: (message?: undefined | SendbirdUserMessage | SendbirdFileMessage) => void;

// reply - only available on group channel
messageToReply?: undefined | SendbirdUserMessage | SendbirdFileMessage;
setMessageToReply?: (message?: undefined | SendbirdUserMessage | SendbirdFileMessage) => void;

// mention
SuggestedMentionList?: (props: SuggestedMentionListProps) => JSX.Element | null;

Expand Down Expand Up @@ -83,9 +87,8 @@ const ChannelInput = (props: ChannelInputProps) => {
messageToEdit,
});
const inputMode = useIIFE(() => {
if (!messageToEdit) return 'send';
if (messageToEdit.isFileMessage()) return 'send';
return 'edit';
if (messageToEdit && !messageToEdit.isFileMessage()) return 'edit';
else return 'send';
});

const mentionAvailable =
Expand Down Expand Up @@ -138,8 +141,8 @@ const ChannelInput = (props: ChannelInputProps) => {
onChangeText={onChangeText}
autoFocus={AUTO_FOCUS}
onSelectionChange={onSelectionChange}
messageToEdit={messageToEdit}
mentionedUsers={mentionedUsers}
messageToEdit={messageToEdit}
setMessageToEdit={setMessageToEdit}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
onPressScrollToBottomButton: (animated?: boolean) => void;

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

renderMessage: (props: {
Expand All @@ -66,6 +68,7 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
nextMessage?: SendbirdMessage;
onPress?: () => void;
onLongPress?: () => void;
onPressParentMessage?: ChannelMessageListProps<T>['onPressParentMessage'];
onShowUserProfile?: UserProfileContextType['show'];
channel: T;
currentUserId?: ChannelMessageListProps<T>['currentUserId'];
Expand All @@ -91,9 +94,11 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
hasNext,
channel,
onEditMessage,
onReplyMessage,
onDeleteMessage,
onResendFailedMessage,
onPressMediaMessage,
onPressParentMessage,
currentUserId,
renderNewMessagesButton,
renderScrollToBottomButton,
Expand All @@ -119,6 +124,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
channel,
currentUserId,
onEditMessage,
onReplyMessage,
onDeleteMessage,
onResendFailedMessage,
onPressMediaMessage,
Expand All @@ -134,6 +140,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
nextMessage: messages[index - 1],
onPress,
onLongPress,
onPressParentMessage,
onShowUserProfile: show,
enableMessageGrouping,
channel,
Expand Down Expand Up @@ -188,11 +195,18 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
currentUserId,
onResendFailedMessage,
onEditMessage,
onReplyMessage,
onDeleteMessage,
onPressMediaMessage,
}: Pick<
ChannelMessageListProps<T>,
'channel' | 'currentUserId' | 'onEditMessage' | 'onDeleteMessage' | 'onResendFailedMessage' | 'onPressMediaMessage'
| 'channel'
| 'currentUserId'
| 'onEditMessage'
| 'onReplyMessage'
| 'onDeleteMessage'
| 'onResendFailedMessage'
| 'onPressMediaMessage'
>) => {
const { colors } = useUIKitTheme();
const { STRINGS } = useLocalization();
Expand Down Expand Up @@ -267,12 +281,26 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
onPress: () => onEditMessage(msg),
},
{
// TODO: disabled if message has a parentMessageId
// disabled: Boolean(msg.parentMessageId),
icon: 'delete',
title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
onPress: () => confirmDelete(msg),
},
);
}

if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
const disabled = Boolean(msg.parentMessageId);
sheetItems.push({
// TODO: Implement disabled to bottom sheet
// disabled,
icon: 'reply',
// TODO: Add reply label
title: disabled ? 'Reply(disabled)' : 'Reply', //'STRINGS.LABELS.CHANNEL_MESSAGE_REPLY',
onPress: () => onReplyMessage?.(msg),
});
}
}
}

Expand Down Expand Up @@ -301,11 +329,25 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
if (!channel.isEphemeral) {
if (isMyMessage(msg, currentUserId) && msg.sendingStatus === 'succeeded') {
sheetItems.push({
// TODO: disabled if message has a parentMessageId
// disabled: Boolean(msg.parentMessageId),
icon: 'delete',
title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
onPress: () => confirmDelete(msg),
});
}

if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
const disabled = Boolean(msg.parentMessageId);
sheetItems.push({
// TODO: Implement disabled to bottom sheet
// disabled,
icon: 'reply',
// TODO: Add reply label
title: disabled ? 'Reply(disabled)' : 'Reply', //'STRINGS.LABELS.CHANNEL_MESSAGE_REPLY',
onPress: () => onReplyMessage?.(msg),
});
}
}

const fileType = getFileType(msg.type || getFileExtension(msg.name));
Expand Down
Loading