import * as React from 'react'; import { ListRenderItemInfo, Pressable, View } from 'react-native'; import { getMessageSnapshot, getMessageSnapshotParams, MessageManagerListener, useChatContext, } from '../../chat'; import { useColors } from '../../hook'; import { useI18nContext } from '../../i18n'; import { ChatMessage, ChatMessagePinInfo } from '../../rename.chat'; import { usePaletteContext } from '../../theme'; import { FlatListFactory } from '../../ui/FlatList'; import { PressableHighlight } from '../../ui/Pressable'; import { SingleLineText } from '../../ui/Text'; import { formatTsForConvDetail } from '../../utils'; import { useDataPriority } from '../hooks/useDataPriority'; import { useFlatList } from '../List'; type DeleteButtonStatus = 'request' | 'confirm'; export type MessagePinListRef = { addPinMessage: (msg: ChatMessage) => void; registerCallback: (onClickedItem: (msg: ChatMessage) => void) => void; }; export type MessagePinListProps = { convId: string; convType: number; onClickedItem?: (msg: ChatMessage) => void; onCountChanged?: (count: number) => void; propsRef?: | React.RefObject | ((ref: React.RefObject) => void); onRequestClose?: () => void; }; export function MessagePinList(props: MessagePinListProps) { const FlatList = React.useMemo( () => FlatListFactory(), [] ); const { data, onClickedItemCallback, onClickedItemDeleteButtonCallback } = useMessagePinList(props); return ( ) => { const { item } = info; return ( ); }} keyExtractor={(item) => `${item.id}`} /> ); } export type MessagePinListItemProps = { id: string; msg: ChatMessage; pinInfo?: ChatMessagePinInfo; buttonStatus?: DeleteButtonStatus; onClickedItem?: (msg: ChatMessage) => void; onClickedItemDeleteButton?: ( buttonStatus: DeleteButtonStatus, msg: ChatMessage ) => void; }; export function MessagePinListItem(props: MessagePinListItemProps) { const { tr } = useI18nContext(); const { getMsgInfo, getContactInfo } = useDataPriority({}); const { colors } = usePaletteContext(); const { getColor } = useColors({ btn: { light: colors.neutralSpecial[5], dark: colors.neutralSpecial[6], }, time: { light: colors?.neutral[7], dark: colors?.neutral[7], }, content: { light: colors?.neutral[4], dark: colors?.neutral[6], }, }); const { msg, pinInfo, buttonStatus, onClickedItem, onClickedItemDeleteButton, } = props; return ( { onClickedItem?.(msg); }} > {pinInfo?.operatorId ? (getContactInfo(pinInfo?.operatorId).name ?? pinInfo?.operatorId) : pinInfo?.operatorId} {tr('_uikit_pin_content_1')} {getMsgInfo(msg).name ?? msg.from} {tr('_uikit_pin_content_2')} {formatTsForConvDetail(pinInfo?.pinTime ?? msg.serverTime)} {tr(getMessageSnapshot(msg), ...getMessageSnapshotParams(msg))} { onClickedItemDeleteButton?.(buttonStatus ?? 'request', msg); }} > {buttonStatus === 'request' ? tr('_uikit_pin_message_button_request_delete') : tr('_uikit_pin_message_button_confirm_delete')} ); } export const MessagePinListItemMemo = React.memo(MessagePinListItem); export function useMessagePinList(props: MessagePinListProps) { const { onClickedItem, convId, convType, propsRef, onCountChanged } = props; const flatListProps = useFlatList({ listState: 'loading', }); const { setData, dataRef } = flatListProps; const im = useChatContext(); const onClickedItemRef = React.useRef<(msg: ChatMessage) => void>(undefined); const removeDuplicateData = React.useCallback( (list: MessagePinListItemProps[]) => { const uniqueList = list.filter( (item, index, self) => index === self.findIndex((t) => t.id === item.id) ); return uniqueList; }, [] ); const sortData = React.useCallback(() => { dataRef.current = dataRef.current.sort((a, b) => { if (!a.pinInfo || !b.pinInfo) { return 0; } return b.pinInfo?.pinTime - a.pinInfo?.pinTime; }); }, [dataRef]); const refreshToUI = React.useCallback( (list: MessagePinListItemProps[]) => { sortData(); dataRef.current = removeDuplicateData(list); setData([...dataRef.current]); }, [dataRef, removeDuplicateData, setData, sortData] ); const addItemToUI = React.useCallback( async (list: ChatMessage[], pos: 'before' | 'after') => { const promise = Promise.all( list.map(async (v) => { const p = await v.getPinInfo; return { id: v.msgId, msg: v, pinInfo: p, buttonStatus: 'request', } as MessagePinListItemProps; }) ); const propsList = await promise; if (pos === 'before') { dataRef.current = [...propsList, ...dataRef.current]; } else { dataRef.current = [...dataRef.current, ...propsList]; } refreshToUI(dataRef.current); onCountChanged?.(dataRef.current.length); }, // eslint-disable-next-line react-hooks/exhaustive-deps [dataRef, refreshToUI] ); const updateItemToUI = React.useCallback( (data: MessagePinListItemProps) => { dataRef.current = dataRef.current.map((item) => { if (item.msg.msgId === data.msg.msgId) { item.msg = { ...item.msg, ...data.msg } as ChatMessage; item.buttonStatus = data.buttonStatus; } return item; }); refreshToUI(dataRef.current); }, [dataRef, refreshToUI] ); const removeItemToUI = React.useCallback( (msgId: string) => { dataRef.current = dataRef.current.filter((item) => item.id !== msgId); refreshToUI(dataRef.current); onCountChanged?.(dataRef.current.length); }, // eslint-disable-next-line react-hooks/exhaustive-deps [dataRef, refreshToUI] ); const onClickedItemCallback = React.useCallback( (data: ChatMessage) => { if (data) { if (onClickedItem) { onClickedItem(data); } else if (onClickedItemRef.current) { onClickedItemRef.current(data); } } }, [onClickedItem] ); const onClickedItemDeleteButtonCallback = React.useCallback( (buttonStatus: DeleteButtonStatus, msg: ChatMessage) => { if (buttonStatus === 'request') { updateItemToUI({ msg: msg, buttonStatus: 'confirm', } as MessagePinListItemProps); } else if (buttonStatus === 'confirm') { im.unPinMessage({ msgId: msg.msgId, onResult: (res) => { if (res.isOk) { removeItemToUI(msg.msgId); } }, }); } }, [im, removeItemToUI, updateItemToUI] ); const addPinMessage = React.useCallback( (msg: ChatMessage) => { im.pinMessage({ msgId: msg.msgId, onResult: (res) => { if (res.isOk) { addItemToUI([msg], 'before'); } }, }); }, [addItemToUI, im] ); const registerCallback = React.useCallback( (onClickedItem: (msg: ChatMessage) => void) => { onClickedItemRef.current = onClickedItem; }, [] ); const maybePropsRef = React.useRef({ addPinMessage: addPinMessage, registerCallback: registerCallback, }); const requestList = React.useCallback( (forceRequest?: boolean) => { im.fetchPinnedMessages({ convId: convId, convType: convType, forceRequest: forceRequest, onResult: (res) => { if (res.isOk && res.value) { const list = res.value; addItemToUI(list, 'before'); } }, }); }, [addItemToUI, convId, convType, im] ); const onRecvRecallMessage = React.useCallback( (_orgMsg: ChatMessage, _tipMsg: ChatMessage) => { dataRef.current = []; requestList(true); }, [dataRef, requestList] ); React.useEffect(() => { requestList(); }, [requestList]); React.useEffect(() => { if (propsRef) { if (typeof propsRef === 'function') { propsRef(maybePropsRef); } else { if (propsRef.current) { propsRef.current.addPinMessage = addPinMessage; propsRef.current.registerCallback = registerCallback; } } } }, [addPinMessage, propsRef, registerCallback]); React.useEffect(() => { const listener: MessageManagerListener = { onPinMessageChanged: (msg: ChatMessage, pinOperation: number) => { if (pinOperation === 0) { addItemToUI([msg], 'before'); } else if (pinOperation === 1) { removeItemToUI(msg.msgId); } }, onRecvRecallMessage: (orgMsg: ChatMessage, tipMsg: ChatMessage) => { if (orgMsg.conversationId === convId) { onRecvRecallMessage(orgMsg, tipMsg); } }, onRecallMessageResult: (params: { isOk: boolean; orgMsg?: ChatMessage; tipMsg?: ChatMessage; }) => { if (params.isOk === true) { if (params.orgMsg && params.tipMsg) { if (params.orgMsg.conversationId === convId) { onRecvRecallMessage(params.orgMsg, params.tipMsg); } } } }, }; im.messageManager.addListener('MessagePinList', listener); return () => { im.messageManager.removeListener('MessagePinList'); }; }, [ addItemToUI, convId, im.messageManager, onRecvRecallMessage, removeItemToUI, ]); return { ...flatListProps, onClickedItemCallback, onClickedItemDeleteButtonCallback, }; }