refactor(PostCard, PostCard.legacy): remove legacy PostCard component and update exports
- Deleted the legacy PostCard component to streamline the codebase and improve maintainability. - Updated exports in PostCard and index files to remove references to the legacy component. - Adjusted PostCardProps to eliminate legacy properties, ensuring only the new API is supported. - Enhanced the PostCard component to focus on modern implementation and features.
This commit is contained in:
@@ -78,6 +78,8 @@ export const ChatScreen: React.FC = () => {
|
||||
// 输入框区域高度(用于定位浮动 mention 面板)
|
||||
const [inputWrapperHeight, setInputWrapperHeight] = useState(60);
|
||||
const [showEdgeLoadingIndicator, setShowEdgeLoadingIndicator] = useState(false);
|
||||
const replyTargetMessageIdRef = useRef<string | null>(null);
|
||||
const replyHighlightTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const isPreloadingRef = useRef(false);
|
||||
const lastScrollYRef = useRef(0);
|
||||
const isUserDraggingRef = useRef(false);
|
||||
@@ -151,6 +153,7 @@ export const ChatScreen: React.FC = () => {
|
||||
longPressMenuVisible,
|
||||
selectedMessage,
|
||||
selectedMessageId,
|
||||
setSelectedMessageId,
|
||||
menuPosition,
|
||||
isGroupChat,
|
||||
groupInfo,
|
||||
@@ -206,6 +209,7 @@ export const ChatScreen: React.FC = () => {
|
||||
handleReachLatestEdge,
|
||||
setBrowsingHistory,
|
||||
} = useChatScreen();
|
||||
const displayMessages = useMemo(() => [...messages].reverse(), [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingMore && showEdgeLoadingIndicator) {
|
||||
@@ -218,14 +222,46 @@ export const ChatScreen: React.FC = () => {
|
||||
if (preloadCooldownTimerRef.current) {
|
||||
clearTimeout(preloadCooldownTimerRef.current);
|
||||
}
|
||||
if (replyHighlightTimerRef.current) {
|
||||
clearTimeout(replyHighlightTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const highlightMessageTemporarily = useCallback((messageId: string, duration = 1500) => {
|
||||
if (replyHighlightTimerRef.current) {
|
||||
clearTimeout(replyHighlightTimerRef.current);
|
||||
}
|
||||
setSelectedMessageId(messageId);
|
||||
replyHighlightTimerRef.current = setTimeout(() => {
|
||||
setSelectedMessageId(prev => (prev === messageId ? null : prev));
|
||||
}, duration);
|
||||
}, [setSelectedMessageId]);
|
||||
|
||||
const handleReplyPreviewPress = useCallback((messageId: string) => {
|
||||
const targetId = String(messageId);
|
||||
const targetIndex = displayMessages.findIndex(msg => String(msg.id) === targetId);
|
||||
if (targetIndex < 0) return;
|
||||
|
||||
replyTargetMessageIdRef.current = targetId;
|
||||
setBrowsingHistory(true);
|
||||
highlightMessageTemporarily(targetId);
|
||||
|
||||
flatListRef.current?.scrollToIndex({
|
||||
index: targetIndex,
|
||||
animated: true,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
}, [displayMessages, flatListRef, highlightMessageTemporarily, setBrowsingHistory]);
|
||||
|
||||
// 监听返回事件,刷新会话列表
|
||||
useEffect(() => {
|
||||
const unsubscribe = navigation.addListener('beforeRemove', () => {
|
||||
// 刷新会话列表,确保已读状态正确显示
|
||||
messageManager.fetchConversations(true);
|
||||
messageManager.requestConversationListRefresh('chat-before-remove', {
|
||||
force: true,
|
||||
allowDefer: false,
|
||||
});
|
||||
});
|
||||
return unsubscribe;
|
||||
}, [navigation]);
|
||||
@@ -261,6 +297,7 @@ export const ChatScreen: React.FC = () => {
|
||||
shouldShowTime={shouldShowTime}
|
||||
onImagePress={handleImagePress}
|
||||
onReply={handleReplyMessage}
|
||||
onReplyPress={handleReplyPreviewPress}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
@@ -280,14 +317,13 @@ export const ChatScreen: React.FC = () => {
|
||||
shouldShowTime,
|
||||
handleImagePress,
|
||||
handleReplyMessage,
|
||||
handleReplyPreviewPress,
|
||||
]);
|
||||
|
||||
const keyExtractor = useCallback((item: any) => String(item.id), []);
|
||||
|
||||
// 获取正在输入提示
|
||||
const typingHint = getTypingHint();
|
||||
const displayMessages = useMemo(() => [...messages].reverse(), [messages]);
|
||||
|
||||
const handleMessageListScroll = useCallback((event: any) => {
|
||||
const { contentSize, contentOffset, layoutMeasurement } = event.nativeEvent;
|
||||
scrollPositionRef.current = {
|
||||
@@ -410,6 +446,26 @@ export const ChatScreen: React.FC = () => {
|
||||
}}
|
||||
scrollEventThrottle={16}
|
||||
onContentSizeChange={handleContentSizeChange}
|
||||
onScrollToIndexFailed={(info) => {
|
||||
const targetId = replyTargetMessageIdRef.current;
|
||||
if (!targetId || !flatListRef.current) return;
|
||||
|
||||
flatListRef.current.scrollToOffset({
|
||||
offset: Math.max(0, info.averageItemLength * info.index),
|
||||
animated: true,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const retryIndex = displayMessages.findIndex(msg => String(msg.id) === targetId);
|
||||
if (retryIndex >= 0) {
|
||||
flatListRef.current?.scrollToIndex({
|
||||
index: retryIndex,
|
||||
animated: true,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
}
|
||||
}, 120);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showEdgeLoadingIndicator && (
|
||||
|
||||
@@ -57,6 +57,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
||||
shouldShowTime,
|
||||
onImagePress,
|
||||
onReply,
|
||||
onReplyPress,
|
||||
}) => {
|
||||
const bubbleRef = useRef<View>(null);
|
||||
const pressPositionRef = useRef({ x: 0, y: 0 });
|
||||
@@ -306,6 +307,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
||||
isMe ? styles.myBubble : styles.theirBubble,
|
||||
hasReply && segmentStyles.replyBubble,
|
||||
isPureImageMessage && segmentStyles.pureImageBubble,
|
||||
isSelected && (isMe ? styles.mySelectedBubble : styles.theirSelectedBubble),
|
||||
]}>
|
||||
<MessageSegmentsRenderer
|
||||
segments={segments}
|
||||
@@ -315,7 +317,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
||||
replyMessage={getReplyMessage()}
|
||||
getSenderInfo={getSenderInfo}
|
||||
onAtPress={() => undefined}
|
||||
onReplyPress={() => undefined}
|
||||
onReplyPress={onReplyPress}
|
||||
onImagePress={(url) => {
|
||||
// 查找点击的图片索引
|
||||
const clickIndex = imageSegments.findIndex(img => img.url === url);
|
||||
|
||||
@@ -86,6 +86,8 @@ export interface MessageBubbleProps {
|
||||
onImagePress?: (images: { id: string; url: string; thumbnail_url?: string; width?: number; height?: number }[], index: number) => void;
|
||||
// 滑动回复回调
|
||||
onReply?: (message: GroupMessage) => void;
|
||||
// 点击回复预览回调(定位到原消息)
|
||||
onReplyPress?: (messageId: string) => void;
|
||||
}
|
||||
|
||||
// 输入框 Props
|
||||
|
||||
@@ -124,6 +124,8 @@ export const useChatScreen = () => {
|
||||
const isProgrammaticScrollRef = useRef(false);
|
||||
const suppressAutoFollowRef = useRef(false);
|
||||
const isBrowsingHistoryRef = useRef(false);
|
||||
const hasShownMessageListRef = useRef(false);
|
||||
const historyLoadingLockUntilRef = useRef(0);
|
||||
|
||||
// 回复消息状态
|
||||
const [replyingTo, setReplyingTo] = useState<GroupMessage | null>(null);
|
||||
@@ -205,9 +207,16 @@ export const useChatScreen = () => {
|
||||
// 加载态语义修正:
|
||||
// isLoadingMessages 在分页加载历史时也会短暂为 true。
|
||||
// 若直接驱动 ChatScreen 的 loading,会导致消息列表被卸载重挂载,触发“回到底部”。
|
||||
// 这里只在“首屏且尚无消息”时展示 loading 占位。
|
||||
// 这里只在“首屏且尚未展示过消息列表”时展示 loading 占位。
|
||||
useEffect(() => {
|
||||
setLoading(isLoadingMessages && messageManagerMessages.length === 0);
|
||||
if (messageManagerMessages.length > 0) {
|
||||
hasShownMessageListRef.current = true;
|
||||
}
|
||||
const shouldShowInitialLoading =
|
||||
!hasShownMessageListRef.current &&
|
||||
isLoadingMessages &&
|
||||
messageManagerMessages.length === 0;
|
||||
setLoading(shouldShowInitialLoading);
|
||||
}, [isLoadingMessages, messageManagerMessages.length]);
|
||||
|
||||
// 【改造】同步 hasMore 状态
|
||||
@@ -281,10 +290,16 @@ export const useChatScreen = () => {
|
||||
enterMarkedKeyRef.current = '';
|
||||
suppressAutoFollowRef.current = false;
|
||||
isBrowsingHistoryRef.current = false;
|
||||
hasShownMessageListRef.current = false;
|
||||
historyLoadingLockUntilRef.current = 0;
|
||||
}, [conversationId]);
|
||||
|
||||
const isHistoryLoadingLocked = useCallback(() => {
|
||||
return Date.now() < historyLoadingLockUntilRef.current;
|
||||
}, []);
|
||||
|
||||
const scrollToLatest = useCallback((animated: boolean, force: boolean = false, reason: string = 'unknown') => {
|
||||
if (!force && (suppressAutoFollowRef.current || isBrowsingHistoryRef.current)) return;
|
||||
if (!force && (suppressAutoFollowRef.current || isBrowsingHistoryRef.current || isHistoryLoadingLocked())) return;
|
||||
// inverted 列表下,最新消息端对应 offset=0
|
||||
isProgrammaticScrollRef.current = true;
|
||||
flatListRef.current?.scrollToOffset({
|
||||
@@ -294,7 +309,7 @@ export const useChatScreen = () => {
|
||||
setTimeout(() => {
|
||||
isProgrammaticScrollRef.current = false;
|
||||
}, animated ? 220 : 32);
|
||||
}, [flatListRef, scrollPositionRef, messages.length]);
|
||||
}, [flatListRef, isHistoryLoadingLocked]);
|
||||
|
||||
const isNearBottom = useCallback(() => {
|
||||
const scrollY = scrollPositionRef.current.scrollY || 0;
|
||||
@@ -443,6 +458,8 @@ export const useChatScreen = () => {
|
||||
|
||||
// 历史加载期间禁止“新消息自动跟随到底”
|
||||
suppressAutoFollowRef.current = true;
|
||||
isBrowsingHistoryRef.current = true;
|
||||
historyLoadingLockUntilRef.current = Date.now() + 3000;
|
||||
|
||||
// 保存加载前的滚动位置和内容高度
|
||||
const scrollYBefore = scrollPositionRef.current.scrollY;
|
||||
@@ -464,6 +481,8 @@ export const useChatScreen = () => {
|
||||
console.error('加载历史消息失败:', error);
|
||||
} finally {
|
||||
setLoadingMore(false);
|
||||
// 加载结束后继续保留短暂锁窗,避免布局结算阶段误触自动回底
|
||||
historyLoadingLockUntilRef.current = Date.now() + 800;
|
||||
}
|
||||
}, [conversationId, hasMoreHistory, loadingMore, loadMoreMessages, messages]);
|
||||
|
||||
@@ -473,9 +492,10 @@ export const useChatScreen = () => {
|
||||
}, []);
|
||||
|
||||
const handleReachLatestEdge = useCallback(() => {
|
||||
if (isHistoryLoadingLocked()) return;
|
||||
suppressAutoFollowRef.current = false;
|
||||
isBrowsingHistoryRef.current = false;
|
||||
}, []);
|
||||
}, [isHistoryLoadingLocked]);
|
||||
|
||||
const setBrowsingHistory = useCallback((browsing: boolean) => {
|
||||
isBrowsingHistoryRef.current = browsing;
|
||||
@@ -1221,6 +1241,7 @@ export const useChatScreen = () => {
|
||||
longPressMenuVisible,
|
||||
selectedMessage,
|
||||
selectedMessageId,
|
||||
setSelectedMessageId,
|
||||
menuPosition,
|
||||
isGroupChat,
|
||||
groupInfo,
|
||||
|
||||
Reference in New Issue
Block a user