/** * 聊天页 ChatScreen * 胡萝卜BBS - 私信/群聊聊天界面 * 高级现代化设计 * 支持群聊功能:显示发送者头像和昵称、@提及功能 * 支持响应式布局(桌面端宽屏优化) * * 重构说明:将原2264行的大文件拆分为多个模块化组件 * - types.ts: 类型定义 * - constants.ts: 常量定义 * - styles.ts: 样式定义 * - useChatScreen.ts: 自定义Hook,管理所有状态和逻辑 * - EmojiPanel.tsx: 表情面板组件 * - MorePanel.tsx: 更多功能面板组件 * - MentionPanel.tsx: @成员选择面板组件 * - LongPressMenu.tsx: 长按菜单组件 * - ChatHeader.tsx: 聊天头部组件 * - MessageBubble.tsx: 消息气泡组件 * - ChatInput.tsx: 输入框组件 */ import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'; import { View, TouchableWithoutFeedback, ActivityIndicator, KeyboardAvoidingView, Platform, } from 'react-native'; import { FlashList } from "@shopify/flash-list"; import { useNavigation } from '@react-navigation/native'; import { StatusBar } from 'expo-status-bar'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Text, ImageGallery, ImageGridItem } from '../../components/common'; import { colors } from '../../theme'; import { messageManager } from '../../stores'; import { useBreakpointGTE } from '../../hooks/useResponsive'; import { useChatScreen, chatScreenStyles as baseStyles, EmojiPanel, MorePanel, MentionPanel, LongPressMenu, ChatHeader, MessageBubble, ChatInput, GroupInfoPanel, PANEL_HEIGHTS, } from './components/ChatScreen'; export const ChatScreen: React.FC = () => { const navigation = useNavigation(); const insets = useSafeAreaInsets(); // 响应式布局 const isWideScreen = useBreakpointGTE('lg'); const styles = baseStyles; // 监听屏幕宽度变化,当变为大屏幕时自动跳转到web端首页 useEffect(() => { if (isWideScreen) { // 导航到大屏幕模式的主界面首页 navigation.reset({ index: 0, routes: [ { name: 'Main', params: { screen: 'HomeTab', params: { screen: 'Home' } } } ] }); } }, [isWideScreen, navigation]); // 输入框区域高度(用于定位浮动 mention 面板) const [inputWrapperHeight, setInputWrapperHeight] = useState(60); const containerStyle = useMemo(() => ([ styles.container, isWideScreen ? { maxWidth: 1200, alignSelf: 'center' as const, width: '100%' as const } : null, ]), [isWideScreen, styles.container]); const listContentStyle = useMemo(() => ([ styles.listContent, isWideScreen ? { paddingHorizontal: 24, maxWidth: 900, alignSelf: 'center' as const } : { paddingHorizontal: 16 }, ]), [isWideScreen, styles.listContent]); const inputWrapperStyle = useMemo(() => ([ styles.inputWrapper, isWideScreen ? { maxWidth: 900, alignSelf: 'center' as const, width: '100%' as const } : null, ]), [isWideScreen, styles.inputWrapper]); // 图片查看器状态 const [showImageViewer, setShowImageViewer] = useState(false); const [chatImages, setChatImages] = useState([]); const [selectedImageIndex, setSelectedImageIndex] = useState(0); // 图片点击处理函数 const handleImagePress = (images: ImageGridItem[], index: number) => { setChatImages(images); setSelectedImageIndex(index); setShowImageViewer(true); }; // 关闭图片查看器 const handleCloseImageViewer = () => { setShowImageViewer(false); }; // 群信息面板状态 const [showGroupInfoPanel, setShowGroupInfoPanel] = useState(false); // 打开群信息面板 const handleGroupInfoPress = useCallback(() => { setShowGroupInfoPanel(true); }, []); // 关闭群信息面板 const handleCloseGroupInfoPanel = useCallback(() => { setShowGroupInfoPanel(false); }, []); const { // 状态 messages, inputText, otherUser, currentUser, currentUserId, keyboardHeight, loading, sending, activePanel, sendingImage, replyingTo, longPressMenuVisible, selectedMessage, selectedMessageId, menuPosition, isGroupChat, groupInfo, groupMembers, currentUserRole, mentionQuery, isMuted, muteAll, followRestrictionHint, canSendPrivateImage, routeGroupId, routeGroupName, otherUserLastReadSeq, messageMap, loadingMore, hasMoreHistory, conversationId, // Refs flatListRef, textInputRef, scrollPositionRef, // 方法 formatTime, shouldShowTime, handleInputChange, handleSend, handleMoreAction, handleInsertEmoji, handleSendSticker, toggleEmojiPanel, toggleMorePanel, closePanel, handleRecall, handleLongPressMessage, hideLongPressMenu, handleDeleteMessage, handleReplyMessage, handleCancelReply, handleAvatarPress, handleAvatarLongPress, handleSelectMention, handleMentionAll, getSenderInfo, getTypingHint, getInputBottom, handleDismiss, navigateToInfo, navigateToChatSettings, loadMoreHistory, handleMessageListContentSizeChange, } = useChatScreen(); // 监听返回事件,刷新会话列表 useEffect(() => { const unsubscribe = navigation.addListener('beforeRemove', () => { // 刷新会话列表,确保已读状态正确显示 messageManager.fetchConversations(true); }); return unsubscribe; }, [navigation]); // 渲染消息气泡 const renderMessage = ({ item, index }: { item: any; index: number }) => ( ); // 获取正在输入提示 const typingHint = getTypingHint(); const handleMessageListScroll = useCallback((event: any) => { const { contentSize, contentOffset } = event.nativeEvent; scrollPositionRef.current = { contentHeight: contentSize.height, scrollY: contentOffset.y, }; }, [scrollPositionRef]); return ( {/* 顶部栏 */} navigation.goBack()} onTitlePress={navigateToInfo} onMorePress={navigateToChatSettings} onGroupInfoPress={handleGroupInfoPress} isWideScreen={isWideScreen} /> {/* 消息列表 */} {loading ? ( ) : ( String(item.id)} contentContainerStyle={listContentStyle as any} showsVerticalScrollIndicator={false} keyboardShouldPersistTaps="handled" refreshing={loadingMore} onRefresh={hasMoreHistory ? loadMoreHistory : undefined} // @ts-ignore FlashList 类型定义问题:estimatedItemSize 是必需属性但类型定义缺失 estimatedItemSize={80} maintainVisibleContentPosition={{ minIndexForVisible: 0, autoscrollToTopThreshold: undefined, } as any} onScroll={handleMessageListScroll} /> )} {/* 底部区域:输入框 + 面板 */} 0 ? keyboardHeight : 0 }}> {/* 输入框区域 */} 0 && { paddingBottom: insets.bottom }]} onLayout={e => setInputWrapperHeight(e.nativeEvent.layout.height)} > { // 输入框获得焦点时,关闭其他面板(但不要关闭键盘) if (activePanel !== 'none' && activePanel !== 'mention') { closePanel(); } }} currentUser={currentUser} otherUser={otherUser} getSenderInfo={getSenderInfo} /> {/* 表情面板 */} {activePanel === 'emoji' && keyboardHeight === 0 && ( )} {/* 更多功能面板 */} {activePanel === 'more' && keyboardHeight === 0 && ( )} {/* 发送图片加载遮罩 */} {sendingImage && ( 发送图片中... )} {/* @成员选择浮层 - 绝对定位在输入框上方,覆盖消息列表 */} {activePanel === 'mention' && ( 0 ? keyboardHeight + inputWrapperHeight : inputWrapperHeight + (insets.bottom > 0 ? insets.bottom : 0), height: PANEL_HEIGHTS.mention, }, ]} > )} {/* 长按菜单 */} {/* 图片查看器 */} ({ id: img.id || img.url || String(Math.random()), url: img.url || img.uri || '' }))} initialIndex={selectedImageIndex} onClose={handleCloseImageViewer} enableSave /> {/* 群信息侧边栏面板 - 仅大屏幕显示 */} { // TODO: 实现置顶功能 console.log('Toggle pin:', pinned); }} onLeaveGroup={() => { // TODO: 实现退出群聊功能 console.log('Leave group'); }} onInviteMembers={() => { // TODO: 实现邀请新成员功能 console.log('Invite members'); }} onViewAllMembers={() => { // TODO: 实现查看全部成员功能 console.log('View all members'); }} /> ); }; export default ChatScreen;