Files
zyt/TUICallKit-Vue3/TUIKit/states/MessageListState/MessageListState.ts
T
2026-03-11 09:49:47 +08:00

167 lines
5.5 KiB
TypeScript

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<string | undefined>;
messageList: Ref<readonly MessageModel[] | undefined>;
hasMoreOlderMessage: Ref<boolean | undefined>;
hasMoreNewerMessage: Ref<boolean | undefined>;
enableReadReceipt: Ref<boolean | undefined>;
isDisableScroll: Ref<boolean | undefined>;
recalledMessageIDSet: Ref<Set<string>>;
highlightMessageIDSet: Ref<Set<string>>;
}
interface MessageListBusinessAction {
loadMoreOlderMessage: () => Promise<void>;
// TODO
// loadMoreNewerMessage: () => Promise<void>;
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<string | undefined>(undefined);
const messageList = ref<readonly MessageModel[] | undefined>(undefined);
const hasMoreOlderMessage = ref<boolean | undefined>(undefined);
const hasMoreNewerMessage = ref<boolean | undefined>(undefined);
const enableReadReceipt = ref<boolean | undefined>(undefined);
const isDisableScroll = ref<boolean | undefined>(undefined);
const recalledMessageIDSet = ref<Set<string>>(new Set());
const highlightMessageIDSet = ref<Set<string>>(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 };