/** * ChatScreen 自定义 Hook * 管理聊天的所有状态和逻辑 * * 【重构后】完全依赖 MessageManager 架构 * - 消息数据通过 MessageManager 管理 * - 会话详情通过 useChat 返回的 conversation 获取 * - 发送者信息通过 MessageManager 维护 * - 输入状态、禁言状态通过 MessageManager 管理 * - 解决竞态条件和状态同步问题 */ import { useState, useRef, useEffect, useCallback, useMemo } from 'react'; import { Keyboard, Platform, KeyboardEvent, Alert, } from 'react-native'; import { useRoute, RouteProp, useNavigation } from '@react-navigation/native'; import { formatDistanceToNow } from 'date-fns'; import { zhCN } from 'date-fns/locale'; import * as ImagePicker from 'expo-image-picker'; import { GroupMemberResponse, MessageSegment, TextSegmentData, ImageSegmentData, AtSegmentData, ReplySegmentData, MessageStatus } from '../../../../types/dto'; import { messageService } from '../../../../services/messageService'; import { uploadService } from '../../../../services/uploadService'; import { ApiError } from '../../../../services/api'; // 【新架构】使用 MessageManager import { useChat, useGroupTyping, useGroupMuted, messageManager } from '../../../../stores'; import { groupService } from '../../../../services/groupService'; import { userManager } from '../../../../stores/userManager'; import { groupManager } from '../../../../stores/groupManager'; import { RootStackParamList } from '../../../../navigation/types'; import { GroupMessage, PanelType, UserRole, SenderInfo, ChatRouteParams, MenuPosition, } from './types'; import { TIME_SEPARATOR_INTERVAL, MEMBERS_PAGE_SIZE } from './constants'; import { deleteMessage as deleteMessageFromDb, clearConversationMessages, } from '../../../../services/database'; type ChatRouteProp = RouteProp; export const useChatScreen = () => { const getSendErrorMessage = useCallback((error: unknown, fallback: string) => { if (error instanceof ApiError && error.message) { return error.message; } return fallback; }, []); const route = useRoute(); const navigation = useNavigation(); // 路由参数 const routeParams = useMemo(() => ({ conversationId: route.params?.conversationId || null, userId: route.params?.userId || null, isGroupChat: route.params?.isGroupChat || false, groupId: route.params?.groupId, groupName: route.params?.groupName, }), [route.params?.conversationId, route.params?.userId, route.params?.isGroupChat, route.params?.groupId, route.params?.groupName]); const { conversationId: routeConversationId, userId: routeUserId, isGroupChat, groupId: routeGroupId, groupName: routeGroupName } = routeParams; // 当前会话ID(可能在新建私聊会话后动态更新) const [conversationId, setConversationId] = useState(routeConversationId); // 【新架构】使用 MessageManager 的 hook 获取所有数据 const { messages: messageManagerMessages, isLoadingMessages, hasMoreMessages, loadMoreMessages, refreshMessages, sendMessage: sendMessageViaManager, isSending: isSendingViaManager, markAsRead, conversation, } = useChat(conversationId); // 本地状态 const [currentUserId, setCurrentUserId] = useState(''); // 【新架构】使用 MessageManager 获取群聊输入状态和禁言状态 const { typingUsers: groupTypingUsers } = useGroupTyping(routeGroupId ? String(routeGroupId) : null); const { isMuted: isMutedFromManager, setMuted: setMutedStatus } = useGroupMuted( routeGroupId ? String(routeGroupId) : null, currentUserId ); // 本地状态 const [inputText, setInputText] = useState(''); const [otherUser, setOtherUser] = useState<{ id?: string; nickname?: string; avatar?: string | null } | null>(null); const [isFollowedByOther, setIsFollowedByOther] = useState(null); const [currentUser, setCurrentUser] = useState<{ id?: string; nickname?: string; avatar?: string | null } | null>(null); const [keyboardHeight, setKeyboardHeight] = useState(0); const [loading, setLoading] = useState(true); const [sending, setSending] = useState(false); const [lastSeq, setLastSeq] = useState(0); const [firstSeq, setFirstSeq] = useState(0); const [otherUserLastReadSeq, setOtherUserLastReadSeq] = useState(0); const [activePanel, setActivePanel] = useState('none'); const [sendingImage, setSendingImage] = useState(false); const [loadingMore, setLoadingMore] = useState(false); const [hasMoreHistory, setHasMoreHistory] = useState(true); const flatListRef = useRef(null); const textInputRef = useRef(null); // 防止重复加载的 ref const shouldAutoScrollOnEnterRef = useRef(true); const autoScrollTimersRef = useRef[]>([]); const scrollPositionRef = useRef({ contentHeight: 0, scrollY: 0 }); // 回复消息状态 const [replyingTo, setReplyingTo] = useState(null); // 长按菜单状态 const [longPressMenuVisible, setLongPressMenuVisible] = useState(false); const [selectedMessage, setSelectedMessage] = useState(null); const [menuPosition, setMenuPosition] = useState(undefined); const [selectedMessageId, setSelectedMessageId] = useState(null); // 群聊相关状态 const [groupInfo, setGroupInfo] = useState<{ name?: string; member_count?: number } | null>(null); const [groupMembers, setGroupMembers] = useState([]); const [currentUserRole, setCurrentUserRole] = useState('member'); const [mentionQuery, setMentionQuery] = useState(''); const [selectedMentions, setSelectedMentions] = useState([]); const [mentionAll, setMentionAll] = useState(false); const [isMuted, setIsMuted] = useState(false); const [muteAll, setMuteAll] = useState(false); // 路由会话ID变化时同步到本地会话状态,确保订阅会随会话切换 useEffect(() => { setConversationId(routeConversationId); }, [routeConversationId]); // 【改造】直接使用 MessageManager 返回的消息 const messages = useMemo(() => { return messageManagerMessages.map(m => ({ id: m.id, conversation_id: m.conversation_id, sender_id: m.sender_id, seq: m.seq, segments: m.segments || [], status: (m.status || 'normal') as MessageStatus, category: m.category, created_at: m.created_at, sender: (m as any).sender, is_system_notice: (m as any).is_system_notice, notice_content: (m as any).notice_content, })); }, [messageManagerMessages]); // 对方用户ID(稳定来源):优先路由参数,其次会话参与者,最后本地 otherUser const otherUserId = useMemo(() => { if (isGroupChat) return null; if (routeUserId) return String(routeUserId); const participants = conversation?.participants || []; if (participants.length > 0) { if (currentUserId) { const target = participants.find((p: any) => String(p.id) !== String(currentUserId)); if (target?.id) return String(target.id); } if (participants.length === 1 && participants[0]?.id) return String(participants[0].id); } return otherUser?.id ? String(otherUser.id) : null; }, [isGroupChat, routeUserId, conversation?.participants, currentUserId, otherUser?.id]); // 私聊陌生人限制:未被对方关注时仅可发送1条文本消息,且禁止图片 const followRestricted = useMemo(() => { if (isGroupChat) return false; // 仅使用独立关系状态源,避免 participants 覆盖造成误判 return isFollowedByOther === false; }, [isGroupChat, isFollowedByOther]); const myPrivateSentCount = useMemo(() => { if (!followRestricted || !currentUserId) return 0; return messages.filter(m => String(m.sender_id) === String(currentUserId)).length; }, [messages, followRestricted, currentUserId]); const canSendFirstPrivateText = !followRestricted || myPrivateSentCount < 1; const canSendPrivateImage = !followRestricted; const followRestrictionHint = useMemo(() => { if (!followRestricted) return null; if (myPrivateSentCount >= 1) { return '对方未关注你前:仅可发送1条文字消息(已达上限)'; } return '对方未关注你前:仅可发送1条文字消息,且不能发送图片'; }, [followRestricted, myPrivateSentCount]); // 【改造】同步加载状态 useEffect(() => { setLoading(isLoadingMessages); }, [isLoadingMessages]); // 【改造】同步 hasMore 状态 useEffect(() => { setHasMoreHistory(hasMoreMessages); }, [hasMoreMessages]); // 【改造】同步发送状态 useEffect(() => { setSending(isSendingViaManager); }, [isSendingViaManager]); // 【改造】同步禁言状态 useEffect(() => { setIsMuted(isMutedFromManager); }, [isMutedFromManager]); // 【改造】从 conversation 对象获取会话详情 useEffect(() => { if (!conversation) return; // 私聊模式:从 conversation.participants 获取对方用户信息 if (!isGroupChat && conversation.participants) { if (!currentUserId) return; const currentUserIdStr = String(currentUserId); const other = conversation.participants.find((p: any) => p.id !== currentUserIdStr); if (other) { setOtherUser(prev => ({ ...(prev || {}), ...other })); } // 获取对方最后阅读位置 setOtherUserLastReadSeq((conversation as any).other_last_read_seq || 0); } }, [conversation, isGroupChat, currentUserId]); // 独立同步私聊对象互关状态,避免 participants 缓存字段不完整或被刷新覆盖 useEffect(() => { if (isGroupChat) return; if (!otherUserId) return; let cancelled = false; const syncRelationState = async () => { try { const detail = await userManager.getUserById(String(otherUserId), true); if (!detail || cancelled) return; setOtherUser(prev => ({ ...(prev || {}), ...detail })); if (typeof (detail as any).is_following_me === 'boolean') { setIsFollowedByOther((detail as any).is_following_me); } else { setIsFollowedByOther(null); } } catch (error) { console.error('同步私聊对象互关状态失败:', error); if (!cancelled) { setIsFollowedByOther(null); } } }; syncRelationState(); return () => { cancelled = true; }; }, [isGroupChat, otherUserId]); // 进入新会话时重置首次自动滚动标记 useEffect(() => { shouldAutoScrollOnEnterRef.current = true; autoScrollTimersRef.current.forEach(clearTimeout); autoScrollTimersRef.current = []; }, [conversationId]); // 组件卸载时清理定时器 useEffect(() => { return () => { autoScrollTimersRef.current.forEach(clearTimeout); autoScrollTimersRef.current = []; }; }, []); // 消息加载完成后滚动到底部 useEffect(() => { if (!loading && !loadingMore && messages.length > 0 && shouldAutoScrollOnEnterRef.current) { const timer = setTimeout(() => { if (shouldAutoScrollOnEnterRef.current) { flatListRef.current?.scrollToEnd({ animated: false }); } }, 500); return () => clearTimeout(timer); } }, [loading, loadingMore, messages.length]); // 获取当前用户信息 useEffect(() => { const fetchCurrentUser = async () => { try { const currentUserData = await userManager.getCurrentUser(); if (currentUserData) { setCurrentUserId(String(currentUserData.id)); setCurrentUser(currentUserData); } } catch (error) { console.error('获取当前用户失败:', error); } }; fetchCurrentUser(); }, []); // 群聊模式:获取群组信息和成员列表 useEffect(() => { if (!isGroupChat || !routeGroupId) return; const fetchGroupInfo = async () => { try { const group = await groupManager.getGroup(routeGroupId); setGroupInfo(group); const membersResponse = await groupManager.getMembers(routeGroupId, 1, MEMBERS_PAGE_SIZE); const members = membersResponse.list || []; setGroupMembers(members); const myMember = members.find((m: any) => m.user_id === currentUserId); if (myMember) { setCurrentUserRole(myMember.role); } try { const myInfo = await groupService.getMyMemberInfo(routeGroupId); setIsMuted(myInfo.is_muted); setMutedStatus(myInfo.is_muted); setMuteAll(myInfo.mute_all); } catch (error) { console.error('获取成员信息失败:', error); } } catch (error) { console.error('获取群组信息失败:', error); Alert.alert('错误', '无法获取群组信息'); } }; if (currentUserId) { fetchGroupInfo(); } }, [isGroupChat, routeGroupId, currentUserId, setMutedStatus]); // 私聊模式:创建或获取会话 useEffect(() => { if (isGroupChat) return; let isCancelled = false; const createOrGetConversation = async () => { if (conversationId) return; if (!routeUserId) return; try { setLoading(true); const response = await messageService.createConversation(routeUserId); if (isCancelled) return; setConversationId(response.id); if (response.participants && response.participants.length > 0) { const other = response.participants.find((p: any) => p.id !== currentUserId); if (other) { setOtherUser(other); } } } catch (error) { console.error('创建会话失败:', error); Alert.alert('错误', '无法创建会话'); } finally { if (!isCancelled) { setLoading(false); } } }; createOrGetConversation(); return () => { isCancelled = true; }; }, [routeUserId, conversationId, currentUserId, isGroupChat]); // 【改造】加载更多历史消息 const loadMoreHistory = useCallback(async () => { if (!conversationId || !hasMoreHistory || loadingMore) { return; } setLoadingMore(true); try { await loadMoreMessages(); // 更新 firstSeq if (messages.length > 0) { const minSeq = Math.min(...messages.map(m => m.seq)); setFirstSeq(minSeq); } } catch (error) { console.error('加载历史消息失败:', error); } finally { setLoadingMore(false); } }, [conversationId, hasMoreHistory, loadingMore, loadMoreMessages, messages]); // 列表内容尺寸变化后触发首次自动滚动 const handleMessageListContentSizeChange = useCallback((contentWidth: number, contentHeight: number) => { if (!shouldAutoScrollOnEnterRef.current) return; if (loading || loadingMore) return; if (messages.length === 0) return; const prevContentHeight = scrollPositionRef.current.contentHeight; scrollPositionRef.current.contentHeight = contentHeight; if (prevContentHeight > 0 && contentHeight > prevContentHeight) { return; } shouldAutoScrollOnEnterRef.current = false; autoScrollTimersRef.current.forEach(clearTimeout); autoScrollTimersRef.current = []; [100, 300, 500, 800, 1200].forEach(delay => { const timer = setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: false }); }, delay); autoScrollTimersRef.current.push(timer); }); }, [loading, loadingMore, messages.length]); // 【改造】自动标记已读 - 当有新消息且是当前会话时 useEffect(() => { if (!conversationId || messages.length === 0) return; const maxSeq = Math.max(...messages.map(m => m.seq), 0); if (maxSeq > 0) { markAsRead(maxSeq).catch(error => { console.error('[ChatScreen] 自动标记已读失败:', error); }); } }, [messages, conversationId, markAsRead]); // 使用 ref 存储 groupMembers const groupMembersRef = useRef(groupMembers); useEffect(() => { groupMembersRef.current = groupMembers; }, [groupMembers]); // 使用 ref 存储 currentUserId const currentUserIdRef = useRef(currentUserId); useEffect(() => { currentUserIdRef.current = currentUserId; }, [currentUserId]); // 监听键盘事件 useEffect(() => { const keyboardWillShow = (e: KeyboardEvent) => { setKeyboardHeight(e.endCoordinates.height); setActivePanel(prev => prev === 'mention' ? 'mention' : 'none'); setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: false }); }, 150); }; const keyboardWillHide = () => { setKeyboardHeight(0); setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: false }); }, 100); }; const showSubscription = Keyboard.addListener( Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', keyboardWillShow ); const hideSubscription = Keyboard.addListener( Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', keyboardWillHide ); return () => { showSubscription.remove(); hideSubscription.remove(); }; }, []); // 格式化时间 const formatTime = useCallback((dateString: string): string => { try { const date = new Date(dateString); const now = new Date(); const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60); if (diffInHours < 24 && date.getDate() === now.getDate()) { return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); } return formatDistanceToNow(date, { addSuffix: true, locale: zhCN, }); } catch { return ''; } }, []); // 检查是否需要显示时间分隔 const shouldShowTime = useCallback((index: number): boolean => { if (index === 0) return true; const currentMsg = messages[index]; const prevMsg = messages[index - 1]; const currentTime = new Date(currentMsg.created_at).getTime(); const prevTime = new Date(prevMsg.created_at).getTime(); return (currentTime - prevTime) > TIME_SEPARATOR_INTERVAL; }, [messages]); // 处理输入变化,检测@符号 const handleInputChange = useCallback((text: string) => { setInputText(text); if (isGroupChat) { const lastAtIndex = text.lastIndexOf('@'); if (lastAtIndex !== -1) { const textAfterAt = text.slice(lastAtIndex + 1); if (!textAfterAt.includes(' ')) { setMentionQuery(textAfterAt.toLowerCase()); setActivePanel('mention'); } else { setActivePanel('none'); } } else { setActivePanel('none'); } } }, [isGroupChat]); // 【改造】获取发送者信息(群聊)- 优先从消息的 sender 字段获取 const getSenderInfo = useCallback((senderId: string): SenderInfo => { if (senderId === currentUserId) { return { nickname: currentUser?.nickname || '我', avatar: currentUser?.avatar || null, userId: currentUserId, }; } // 优先从消息列表中查找 sender 信息 const messageWithSender = messages.find(m => m.sender_id === senderId && m.sender); if (messageWithSender?.sender) { return { nickname: messageWithSender.sender.nickname || messageWithSender.sender.username || '用户', avatar: messageWithSender.sender.avatar || null, userId: senderId, }; } // 兜底:从群成员列表查找 const member = groupMembers.find(m => m.user_id === senderId); if (member) { return { nickname: member.nickname || member.user?.nickname || '用户', avatar: member.user?.avatar || null, userId: member.user_id, }; } return { nickname: '用户', avatar: null, userId: senderId, }; }, [currentUserId, currentUser, messages, groupMembers]); // 创建消息映射(用于查找被回复的消息) const messageMap = useMemo(() => { const map = new Map(); messages.forEach(msg => { map.set(String(msg.id), msg); }); return map; }, [messages]); // 选择@用户 const handleSelectMention = useCallback((member: { user_id: string; nickname?: string; user?: { nickname?: string } }) => { const lastAtIndex = inputText.lastIndexOf('@'); if (lastAtIndex !== -1) { const textBeforeAt = inputText.slice(0, lastAtIndex); const displayName = member.nickname || member.user?.nickname || '用户'; const newText = `${textBeforeAt}@${displayName} `; setInputText(newText); if (!selectedMentions.includes(member.user_id)) { setSelectedMentions(prev => [...prev, member.user_id]); } } setActivePanel('none'); setMentionQuery(''); setTimeout(() => { textInputRef.current?.focus(); }, 100); }, [inputText, selectedMentions]); // @所有人 const handleMentionAll = useCallback(() => { const lastAtIndex = inputText.lastIndexOf('@'); if (lastAtIndex !== -1) { const textBeforeAt = inputText.slice(0, lastAtIndex); const newText = `${textBeforeAt}@所有人 `; setInputText(newText); setMentionAll(true); } setActivePanel('none'); setMentionQuery(''); setTimeout(() => { textInputRef.current?.focus(); }, 100); }, [inputText]); /** * 构建文本消息的 segments 数组 */ const buildTextSegments = useCallback((text: string, replyToMessage?: GroupMessage | null): MessageSegment[] => { const segments: MessageSegment[] = []; if (replyToMessage) { segments.push({ type: 'reply', data: { id: replyToMessage.id, seq: replyToMessage.seq } as ReplySegmentData }); } if (mentionAll) { segments.push({ type: 'at', data: { user_id: 'all' } as AtSegmentData }); } if (selectedMentions.length > 0) { selectedMentions.forEach(userId => { segments.push({ type: 'at', data: { user_id: userId } as AtSegmentData }); }); } let strippedText = text; if (mentionAll) { strippedText = strippedText.replace(/@所有人\s*/g, ''); } if (selectedMentions.length > 0) { selectedMentions.forEach(userId => { const member = groupMembers.find(m => m.user_id === userId); const nickname = member?.nickname || member?.user?.nickname || '用户'; const escapedName = nickname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); strippedText = strippedText.replace(new RegExp(`@${escapedName}\s*`, 'g'), ''); }); } strippedText = strippedText.trim(); if (strippedText) { segments.push({ type: 'text', data: { text: strippedText } as TextSegmentData }); } return segments; }, [mentionAll, selectedMentions, groupMembers]); /** * 构建图片消息的 segments 数组 */ const buildImageSegments = useCallback((imageUrl: string, thumbnailUrl?: string, width?: number, height?: number, replyToMessage?: GroupMessage | null): MessageSegment[] => { const segments: MessageSegment[] = []; if (replyToMessage) { segments.push({ type: 'reply', data: { id: replyToMessage.id, seq: replyToMessage.seq } as ReplySegmentData }); } segments.push({ type: 'image', data: { url: imageUrl, thumbnail_url: thumbnailUrl || imageUrl, width: width, height: height, } as ImageSegmentData }); return segments; }, []); // 【新架构】发送消息 const handleSend = useCallback(async () => { const trimmedText = inputText.trim(); if (!trimmedText || !conversationId) return; if (isGroupChat && isMuted) { if (muteAll) { Alert.alert('无法发送', '当前群组已开启全员禁言'); } else { Alert.alert('无法发送', '你已被管理员禁言'); } return; } if (!isGroupChat && !canSendFirstPrivateText) { Alert.alert('无法发送', '对方未关注你前,仅允许发送一条消息'); return; } setSending(true); try { const segments = buildTextSegments(trimmedText, replyingTo); if (isGroupChat && routeGroupId) { await messageService.sendMessageByAction('group', conversationId, segments); setInputText(''); setSelectedMentions([]); setMentionAll(false); setReplyingTo(null); } else { // 【新架构】私聊消息通过 MessageManager 发送 await sendMessageViaManager(segments); setInputText(''); setReplyingTo(null); } setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); }, 100); } catch (error) { console.error('发送消息失败:', error); Alert.alert('发送失败', getSendErrorMessage(error, '消息发送失败,请重试')); } finally { setSending(false); } }, [inputText, conversationId, isGroupChat, routeGroupId, selectedMentions, mentionAll, sendMessageViaManager, isMuted, muteAll, buildTextSegments, replyingTo, getSendErrorMessage, canSendFirstPrivateText]); // 发送图片消息 const handleSendImage = useCallback(async (imageUri: string, mimeType?: string) => { if (!conversationId) return; if (isGroupChat && isMuted) { if (muteAll) { Alert.alert('无法发送', '当前群组已开启全员禁言'); } else { Alert.alert('无法发送', '你已被管理员禁言'); } return; } if (!isGroupChat && !canSendPrivateImage) { Alert.alert('无法发送', '对方未关注你前,暂不支持发送图片'); return; } setSendingImage(true); try { const uploadResult = await uploadService.uploadChatImage({ uri: imageUri, type: mimeType }); if (!uploadResult) { Alert.alert('上传失败', '图片上传失败,请重试'); return; } const segments = buildImageSegments(uploadResult.url, uploadResult.url, undefined, undefined, replyingTo); if (isGroupChat && routeGroupId) { await messageService.sendMessageByAction('group', conversationId, segments); setReplyingTo(null); } else { // 【新架构】私聊图片通过 MessageManager 发送 await sendMessageViaManager(segments); setReplyingTo(null); } setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); }, 100); } catch (error) { console.error('发送图片失败:', error); Alert.alert('发送失败', getSendErrorMessage(error, '图片发送失败,请重试')); } finally { setSendingImage(false); } }, [conversationId, isGroupChat, routeGroupId, sendMessageViaManager, isMuted, muteAll, buildImageSegments, replyingTo, getSendErrorMessage, canSendPrivateImage]); // 选择图片 const handlePickImage = useCallback(async () => { try { const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== 'granted') { Alert.alert('权限不足', '需要访问相册权限才能选择图片'); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: 'images', allowsEditing: false, quality: 1, allowsMultipleSelection: false, }); if (!result.canceled && result.assets && result.assets.length > 0) { const asset = result.assets[0]; await handleSendImage(asset.uri, asset.mimeType ?? undefined); } } catch (error) { console.error('选择图片失败:', error); Alert.alert('错误', '选择图片失败'); } setActivePanel('none'); }, [handleSendImage]); // 拍摄照片 const handleTakePhoto = useCallback(async () => { try { const { status } = await ImagePicker.requestCameraPermissionsAsync(); if (status !== 'granted') { Alert.alert('权限不足', '需要相机权限才能拍摄照片'); return; } const result = await ImagePicker.launchCameraAsync({ mediaTypes: 'images', allowsEditing: false, quality: 0.8, }); if (!result.canceled && result.assets && result.assets.length > 0) { const asset = result.assets[0]; await handleSendImage(asset.uri, asset.mimeType ?? undefined); } } catch (error) { console.error('拍摄照片失败:', error); Alert.alert('错误', '拍摄照片失败'); } setActivePanel('none'); }, [handleSendImage]); // 处理更多功能 const handleMoreAction = useCallback((actionId: string) => { switch (actionId) { case 'image': handlePickImage(); break; case 'camera': handleTakePhoto(); break; case 'file': Alert.alert('提示', '文件功能即将上线'); setActivePanel('none'); break; case 'location': Alert.alert('提示', '位置功能即将上线'); setActivePanel('none'); break; default: setActivePanel('none'); } }, [handlePickImage, handleTakePhoto]); // 插入表情 const handleInsertEmoji = useCallback((emoji: string) => { setInputText(prev => prev + emoji); }, []); // 发送自定义表情 const handleSendSticker = useCallback(async (stickerUrl: string) => { if (!conversationId) return; if (isGroupChat && isMuted) { if (muteAll) { Alert.alert('无法发送', '当前群组已开启全员禁言'); } else { Alert.alert('无法发送', '你已被管理员禁言'); } return; } if (!isGroupChat && !canSendPrivateImage) { Alert.alert('无法发送', '对方未关注你前,暂不支持发送图片'); return; } setSendingImage(true); try { const segments = buildImageSegments(stickerUrl, stickerUrl, undefined, undefined, replyingTo); if (isGroupChat && routeGroupId) { await messageService.sendMessageByAction('group', conversationId, segments); setReplyingTo(null); } else { // 【新架构】私聊表情通过 MessageManager 发送 await sendMessageViaManager(segments); setReplyingTo(null); } setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); }, 100); } catch (error) { console.error('发送自定义表情失败:', error); Alert.alert('发送失败', '自定义表情发送失败,请重试'); } finally { setSendingImage(false); } }, [conversationId, isGroupChat, routeGroupId, sendMessageViaManager, isMuted, muteAll, buildImageSegments, replyingTo, canSendPrivateImage]); // 切换表情面板 const toggleEmojiPanel = useCallback(() => { Keyboard.dismiss(); setActivePanel(prev => { const newPanel = prev === 'emoji' ? 'none' : 'emoji'; if (newPanel !== 'none') { setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: false }); }, 200); } return newPanel; }); }, []); // 切换更多功能面板 const toggleMorePanel = useCallback(() => { Keyboard.dismiss(); setActivePanel(prev => { const newPanel = prev === 'more' ? 'none' : 'more'; if (newPanel !== 'none') { setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: false }); }, 200); } return newPanel; }); }, []); // 关闭面板 const closePanel = useCallback(() => { setActivePanel('none'); }, []); // 撤回消息 - 现在通过 MessageManager 处理 const handleRecall = useCallback(async (messageId: string) => { try { if (isGroupChat && routeGroupId) { await messageService.recallMessage(messageId); } else { await messageService.recallMessage(messageId); } // 不需要手动更新状态,MessageManager 会处理撤回事件 } catch (error) { console.error('撤回消息失败:', error); Alert.alert('撤回失败', '无法撤回消息'); } }, [isGroupChat, routeGroupId, conversationId]); // 长按消息显示操作菜单 const handleLongPressMessage = useCallback((message: GroupMessage, position?: MenuPosition) => { const isSystemNotice = message.is_system_notice || message.category === 'notification'; if (message.status === 'recalled' || isSystemNotice) return; setSelectedMessage(message); setSelectedMessageId(String(message.id)); setMenuPosition(position); setLongPressMenuVisible(true); }, []); // 隐藏长按菜单 const hideLongPressMenu = useCallback(() => { setLongPressMenuVisible(false); setSelectedMessage(null); setSelectedMessageId(null); setMenuPosition(undefined); }, []); // 删除消息 const handleDeleteMessage = useCallback(async (messageId: string) => { try { await messageService.deleteMessage(messageId); // 从本地状态移除(MessageManager 不会自动处理删除) const updatedMessages = messages.filter(m => m.id !== messageId); // 注意:这里我们依赖 MessageManager 的数据,所以需要通过其他方式刷新 // 暂时只删除本地数据库 await deleteMessageFromDb(messageId); } catch (error) { console.error('删除消息失败:', error); Alert.alert('删除失败', '无法删除消息'); } }, [messages]); // 清空会话的所有聊天记录 const handleClearConversation = useCallback(async () => { if (!conversationId) return; try { setLastSeq(0); setFirstSeq(0); setHasMoreHistory(true); await clearConversationMessages(conversationId); // 刷新消息列表 await refreshMessages(); } catch (error) { console.error('清空会话失败:', error); Alert.alert('清空失败', '无法清空聊天记录'); } }, [conversationId, refreshMessages]); // 回复消息 const handleReplyMessage = useCallback((message: GroupMessage) => { setReplyingTo(message); textInputRef.current?.focus(); hideLongPressMenu(); }, [hideLongPressMenu]); // 取消回复 const handleCancelReply = useCallback(() => { setReplyingTo(null); }, []); // 点击头像跳转到用户主页 const handleAvatarPress = useCallback((userId: string) => { if (userId && userId !== currentUserId) { navigation.navigate('UserProfile' as any, { userId: String(userId) }); } }, [navigation, currentUserId]); // 长按头像@用户 const handleAvatarLongPress = useCallback((senderId: string, nickname: string) => { if (!isGroupChat || senderId === currentUserId) return; if (!selectedMentions.includes(senderId)) { setSelectedMentions(prev => [...prev, senderId]); } const newText = inputText + `@${nickname} `; setInputText(newText); setTimeout(() => { textInputRef.current?.focus(); }, 100); }, [isGroupChat, currentUserId, inputText, selectedMentions]); // 获取正在输入提示文本 - 现在从 MessageManager 获取 const getTypingHint = useCallback((): string | null => { if (groupTypingUsers.length === 0) return null; const typingNames = groupTypingUsers.map(userId => { const member = groupMembers.find(m => m.user_id === userId); return member?.nickname || member?.user?.nickname || '有人'; }); if (typingNames.length === 1) { return `${typingNames[0]} 正在输入...`; } else if (typingNames.length <= 3) { return `${typingNames.join('、')} 正在输入...`; } else { return `${typingNames.slice(0, 3).join('、')}等 ${groupTypingUsers.length} 人正在输入...`; } }, [groupTypingUsers, groupMembers]); // 计算底部面板高度 const getPanelHeight = useCallback(() => { if (activePanel === 'none') return 0; if (activePanel === 'mention') return 250; return 350; }, [activePanel]); // 计算输入框的bottom值 const getInputBottom = useCallback(() => { if (keyboardHeight > 0) return keyboardHeight; if (activePanel !== 'none') return getPanelHeight(); return 0; }, [keyboardHeight, activePanel, getPanelHeight]); // 计算消息列表的底部padding const getListPaddingBottom = useCallback(() => { if (activePanel !== 'none' && keyboardHeight === 0) return getPanelHeight(); return 0; }, [keyboardHeight, activePanel, getPanelHeight]); // 关闭所有面板 const handleDismiss = useCallback(() => { if (activePanel !== 'none') { Keyboard.dismiss(); setActivePanel('none'); } }, [activePanel]); // 导航到群组信息或用户资料 const navigateToInfo = useCallback(() => { if (isGroupChat && routeGroupId) { navigation.navigate('GroupInfo' as any, { groupId: routeGroupId, conversationId: conversationId || undefined, }); } else if (otherUser?.id) { const rootNavigation = navigation.getParent(); rootNavigation?.navigate('UserProfile' as any, { userId: String(otherUser.id) }); } }, [navigation, isGroupChat, routeGroupId, otherUser]); // 导航到聊天管理页面 const navigateToChatSettings = useCallback(() => { if (isGroupChat && routeGroupId) { navigation.navigate('GroupInfo' as any, { groupId: routeGroupId, conversationId: conversationId || undefined, }); } else if (otherUser?.id && conversationId) { navigation.navigate('PrivateChatInfo' as any, { conversationId: conversationId, userId: String(otherUser.id), userName: otherUser.nickname, userAvatar: otherUser.avatar, }); } }, [navigation, isGroupChat, routeGroupId, otherUser, conversationId]); return { // 状态 messages, conversationId, inputText, otherUser, currentUser, currentUserId, keyboardHeight, loading, sending, activePanel, sendingImage, replyingTo, longPressMenuVisible, selectedMessage, selectedMessageId, menuPosition, isGroupChat, groupInfo, groupMembers, // 【改造】使用 MessageManager 的输入状态 typingUsers: groupTypingUsers, currentUserRole, mentionQuery, selectedMentions, isMuted, muteAll, followRestrictionHint, canSendPrivateImage, routeGroupId, routeGroupName, otherUserLastReadSeq, messageMap, loadingMore, hasMoreHistory, // Refs flatListRef, textInputRef, scrollPositionRef, // 方法 formatTime, shouldShowTime, handleInputChange, handleSend, handleSendImage, handlePickImage, handleTakePhoto, handleMoreAction, handleInsertEmoji, handleSendSticker, toggleEmojiPanel, toggleMorePanel, closePanel, handleRecall, handleLongPressMessage, hideLongPressMenu, handleDeleteMessage, handleReplyMessage, handleCancelReply, handleAvatarPress, handleAvatarLongPress, handleSelectMention, handleMentionAll, getSenderInfo, getTypingHint, getPanelHeight, getInputBottom, getListPaddingBottom, handleDismiss, navigateToInfo, navigateToChatSettings, loadMoreHistory, handleClearConversation, handleMessageListContentSizeChange, }; };