import React, { useCallback, useMemo, useState } from 'react'; import { ReplyType } from '@sendbird/chat/message'; import { useGroupChannelMessages } from '@gathertown/uikit-chat-hooks'; import { NOOP, PASS, SendbirdFileMessage, SendbirdGroupChannel, SendbirdUserMessage, messageComparator, useFreshCallback, useIIFE, useRefTracker, } from '@gathertown/uikit-utils'; import GroupChannelMessageRenderer from '../components/GroupChannelMessageRenderer'; import NewMessagesButton from '../components/NewMessagesButton'; import ScrollToBottomButton from '../components/ScrollToBottomButton'; import StatusComposition from '../components/StatusComposition'; import createGroupChannelModule from '../domain/groupChannel/module/createGroupChannelModule'; import type { GroupChannelFragment, GroupChannelModule, GroupChannelProps, GroupChannelPubSubContextPayload, } from '../domain/groupChannel/types'; import { useSendbirdChat } from '../hooks/useContext'; import pubsub from '../utils/pubsub'; const createGroupChannelFragment = (initModule?: Partial): GroupChannelFragment => { const GroupChannelModule = createGroupChannelModule(initModule); return ({ searchItem, renderNewMessagesButton = (props) => , renderScrollToBottomButton = (props) => , renderMessage, enableMessageGrouping = true, enableTypingIndicator, onPressHeaderLeft = NOOP, onPressHeaderRight = NOOP, onPressMediaMessage = NOOP, onChannelDeleted = NOOP, onBeforeSendUserMessage = PASS, onBeforeSendFileMessage = PASS, onBeforeUpdateUserMessage = PASS, onBeforeUpdateFileMessage = PASS, onAfterSendUserMessage = PASS, onAfterSendFileMessage = PASS, onAfterUpdateUserMessage = PASS, onAfterUpdateFileMessage = PASS, onAfterDeleteMessage = PASS, channel, keyboardAvoidOffset, collectionCreator, sortComparator = messageComparator, flatListProps, }) => { const { sdk, currentUser, sbOptions } = useSendbirdChat(); const [internalSearchItem, setInternalSearchItem] = useState(searchItem); const navigateFromMessageSearch = useCallback(() => Boolean(searchItem), []); const [groupChannelPubSub] = useState(() => pubsub()); const [scrolledAwayFromBottom, setScrolledAwayFromBottom] = useState(false); const scrolledAwayFromBottomRef = useRefTracker(scrolledAwayFromBottom); const replyType = useIIFE(() => { if (sbOptions.uikit.groupChannel.channel.replyType === 'none') return ReplyType.NONE; else return ReplyType.ONLY_REPLY_TO_CHANNEL; }); const { loading, messages, newMessages, resetNewMessages, next, prev, hasNext, sendFileMessage, sendUserMessage, updateFileMessage, updateUserMessage, resendMessage, deleteMessage, resetWithStartingPoint, } = useGroupChannelMessages(sdk, channel, currentUser?.userId, { shouldCountNewMessages: () => scrolledAwayFromBottomRef.current, onMessagesReceived(messages) { groupChannelPubSub.publish({ type: 'MESSAGES_RECEIVED', data: { messages } }); }, onMessagesUpdated(messages) { groupChannelPubSub.publish({ type: 'MESSAGES_UPDATED', data: { messages } }); }, collectionCreator, sortComparator, onChannelDeleted, replyType, startingPoint: internalSearchItem?.startingPoint, enableCollectionWithoutLocalCache: true, }); const renderItem: GroupChannelProps['MessageList']['renderMessage'] = useFreshCallback((props) => { if (renderMessage) return renderMessage(props); return ; }); const memoizedFlatListProps = useMemo( () => ({ ListEmptyComponent: , contentContainerStyle: { flexGrow: 1 }, ...flatListProps, }), [flatListProps], ); const onResetMessageList = useCallback((callback?: () => void) => { resetWithStartingPoint(Number.MAX_SAFE_INTEGER, callback); }, []); const onResetMessageListWithStartingPoint = useCallback((startingPoint: number, callback?: () => void) => { resetWithStartingPoint(startingPoint, callback); }, []); // Changing the search item will trigger the focus animation on messages. const onUpdateSearchItem: GroupChannelProps['MessageList']['onUpdateSearchItem'] = useCallback((searchItem) => { // Clean up for animation trigger with useEffect setInternalSearchItem(undefined); setInternalSearchItem(searchItem); }, []); const onPending = (message: SendbirdFileMessage | SendbirdUserMessage) => { groupChannelPubSub.publish({ type: 'MESSAGE_SENT_PENDING', data: { message } }); }; const onSent = (message: SendbirdFileMessage | SendbirdUserMessage) => { groupChannelPubSub.publish({ type: 'MESSAGE_SENT_SUCCESS', data: { message } }); }; const onPressSendUserMessage: GroupChannelProps['Input']['onPressSendUserMessage'] = useFreshCallback( async (params) => { const processedParams = await onBeforeSendUserMessage(params); const message = await sendUserMessage(processedParams, onPending); await onAfterSendUserMessage(message); onSent(message); }, ); const onPressSendFileMessage: GroupChannelProps['Input']['onPressSendFileMessage'] = useFreshCallback( async (params) => { const processedParams = await onBeforeSendFileMessage(params); const message = await sendFileMessage(processedParams, onPending); await onAfterSendFileMessage(message); onSent(message); }, ); const onPressUpdateUserMessage: GroupChannelProps['Input']['onPressUpdateUserMessage'] = useFreshCallback( async (message, params) => { const processedParams = await onBeforeUpdateUserMessage(params); await updateUserMessage(message.messageId, processedParams); await onAfterUpdateUserMessage(message); }, ); const onPressUpdateFileMessage: GroupChannelProps['Input']['onPressUpdateFileMessage'] = useFreshCallback( async (message, params) => { const processedParams = await onBeforeUpdateFileMessage(params); await updateFileMessage(message.messageId, processedParams); await onAfterUpdateFileMessage(message); }, ); const onScrolledAwayFromBottom = useFreshCallback((value: boolean) => { if (!value) resetNewMessages(); setScrolledAwayFromBottom(value); }); return ( }> { await deleteMessage(message); await onAfterDeleteMessage(message); }} onPressMediaMessage={onPressMediaMessage} flatListProps={memoizedFlatListProps} /> ); }; }; function shouldRenderInput(channel: SendbirdGroupChannel) { if (channel.isBroadcast) { return channel.myRole === 'operator'; } return true; } export default createGroupChannelFragment;