import type { Ref } from 'vue'; import { ref } from 'vue'; import { TUIStore, StoreName, TUIChatService, } from '../chat-uikit-engine-lite'; import type { IMessageModel as MessageModel } from '../chat-uikit-engine-lite'; interface MessageListState { activeConversationID: Ref; messageList: Ref; hasMoreOlderMessage: Ref; hasMoreNewerMessage: Ref; enableReadReceipt: Ref; isDisableScroll: Ref; recalledMessageIDSet: Ref>; highlightMessageIDSet: Ref>; } interface MessageListBusinessAction { loadMoreOlderMessage: () => Promise; // TODO // loadMoreNewerMessage: () => Promise; setEnableReadReceipt: (enableReadReceipt: boolean | undefined) => void; setIsDisableScroll: (isDisableScroll: boolean) => void; highlightMessage: ({ messageID, duration }: { messageID: string; duration: number }) => void; } // internal state let prevConversationID: string | undefined; // initial state const activeConversationID = ref(undefined); const messageList = ref(undefined); const hasMoreOlderMessage = ref(undefined); const hasMoreNewerMessage = ref(undefined); const enableReadReceipt = ref(undefined); const isDisableScroll = ref(undefined); const recalledMessageIDSet = ref>(new Set()); const highlightMessageIDSet = ref>(new Set()); function initialState() { activeConversationID.value = undefined; messageList.value = undefined; hasMoreOlderMessage.value = undefined; hasMoreNewerMessage.value = undefined; enableReadReceipt.value = undefined; isDisableScroll.value = undefined; recalledMessageIDSet.value = new Set(); highlightMessageIDSet.value = new Set(); } // business actions function setEnableReadReceipt(_enableReadReceipt: boolean | undefined): void { enableReadReceipt.value = _enableReadReceipt; } function setIsDisableScroll(_isDisableScroll: boolean): void { isDisableScroll.value = _isDisableScroll; } function highlightMessage({ messageID, duration }: { messageID: string; duration: number }): void { highlightMessageIDSet.value.add(messageID); setTimeout(() => { highlightMessageIDSet.value.delete(messageID); }, duration); } function useMessageListState(): MessageListState & MessageListBusinessAction { return { // state activeConversationID, messageList, hasMoreOlderMessage, hasMoreNewerMessage, enableReadReceipt, isDisableScroll, recalledMessageIDSet, highlightMessageIDSet, // actions loadMoreOlderMessage: TUIChatService.getMessageList, // TODO // loadMoreNewerMessage: TUIChatService.getMessageList, setEnableReadReceipt, setIsDisableScroll, highlightMessage, }; } const initMessageListWatcher = () => { /** watcher binding */ function onMessageListUpdated(_messageList: MessageModel[]) { if (!activeConversationID.value) { return; } function createOptimizedMessage(originalMessage: MessageModel): MessageModel { // use spread operator to copy all own properties, but will lose the prototype chain methods return { ...originalMessage, // 🎯 re-create frequently changing properties to ensure React can detect changes status: originalMessage.status, progress: originalMessage.progress, isRevoked: originalMessage.isRevoked, isDeleted: originalMessage.isDeleted, isPeerRead: originalMessage.isPeerRead, // 🎯 re-create read receipt info object readReceiptInfo: originalMessage.readReceiptInfo ? { ...originalMessage.readReceiptInfo, readCount: originalMessage.readReceiptInfo.readCount, unreadCount: originalMessage.readReceiptInfo.unreadCount, isPeerRead: originalMessage.readReceiptInfo.isPeerRead, } : originalMessage.readReceiptInfo, getMessageContent: () => originalMessage.getMessageContent(), deleteMessage: () => originalMessage.deleteMessage(), revokeMessage: () => originalMessage.revokeMessage(), resendMessage: () => originalMessage.resendMessage(), getSignalingInfo: () => originalMessage.getSignalingInfo(), modifyMessage: options => originalMessage.modifyMessage(options), quoteMessage: () => originalMessage.quoteMessage(), replyMessage: () => originalMessage.replyMessage(), }; } const optimizedMessageList = _messageList .map(createOptimizedMessage); messageList.value = optimizedMessageList; // resolve recalled message ID for quoted message messageList.value.forEach((message) => { if (message.isRevoked) { recalledMessageIDSet.value.add(message.ID); } }); } function onMessageListLoadStateUpdated(isCompleted: boolean) { hasMoreOlderMessage.value = !isCompleted; } function onActiveConversationIDUpdated(conversationID: string) { if (!conversationID || conversationID !== prevConversationID) { initialState(); activeConversationID.value = conversationID; } prevConversationID = conversationID || undefined; } TUIStore.watch(StoreName.CONV, { currentConversationID: onActiveConversationIDUpdated, }); TUIStore.watch(StoreName.CHAT, { messageList: onMessageListUpdated, isCompleted: onMessageListLoadStateUpdated, }); }; initMessageListWatcher(); export { useMessageListState };