refactor(PostCard, PostCard.legacy): remove legacy PostCard component and update exports
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 8m37s
Frontend CI / ota-android (push) Successful in 10m53s
Frontend CI / build-android-apk (push) Successful in 51m16s

- 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:
lafay
2026-03-24 04:23:13 +08:00
parent 82e99d24d8
commit 357c1d4995
24 changed files with 1662 additions and 2243 deletions

View File

@@ -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 && (

View File

@@ -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);

View File

@@ -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

View File

@@ -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,