/** * MessageManager React Hooks * * 提供React组件与MessageManager的集成 * 所有hooks都基于MessageManager的订阅机制 * 确保组件能实时获取状态更新 */ import { useState, useEffect, useCallback, useRef } from 'react'; import { ConversationResponse, MessageResponse, MessageSegment } from '../types/dto'; import { messageManager, MessageEvent } from './messageManager'; // ==================== useConversations - 获取会话列表 ==================== interface UseConversationsReturn { conversations: ConversationResponse[]; isLoading: boolean; refresh: () => Promise; } /** * 获取会话列表 * 用于MessageListScreen等需要显示会话列表的组件 */ export function useConversations(): UseConversationsReturn { const [conversations, setConversations] = useState( () => messageManager.getConversations() ); const [isLoading, setIsLoading] = useState(() => messageManager.isLoading()); useEffect(() => { // 订阅MessageManager的事件 const unsubscribe = messageManager.subscribe((event: MessageEvent) => { switch (event.type) { case 'conversations_updated': setConversations(messageManager.getConversations()); break; case 'connection_changed': // 连接状态变化时可能需要刷新 break; } }); // 初始化时确保 MessageManager 已初始化 messageManager.initialize().catch(error => { console.error('[useConversations] 初始化失败:', error); }); // 冷启动兜底:延迟做一次强制刷新,避免首次因时序问题拿到空列表 const coldStartSyncTimer = setTimeout(() => { messageManager.fetchConversations(true).catch(error => { console.error('[useConversations] 冷启动强制刷新失败:', error); }); }, 1200); return () => { clearTimeout(coldStartSyncTimer); unsubscribe(); }; }, []); const refresh = useCallback(async () => { setIsLoading(true); await messageManager.fetchConversations(true); setIsLoading(false); }, []); return { conversations, isLoading, refresh, }; } // ==================== useMessages - 获取指定会话的消息 ==================== interface UseMessagesReturn { messages: MessageResponse[]; isLoading: boolean; hasMore: boolean; loadMore: () => Promise; refresh: () => Promise; } /** * 获取指定会话的消息 * 用于ChatScreen等需要显示消息的组件 * * @param conversationId 会话ID */ export function useMessages(conversationId: string | null): UseMessagesReturn { const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState(true); useEffect(() => { if (!conversationId) { setMessages([]); return; } const normalizedConversationId = String(conversationId); setIsLoading(true); setHasMore(true); let isUnmounted = false; const unsubscribe = messageManager.subscribe((event: MessageEvent) => { if ( event.type === 'messages_updated' && String(event.payload.conversationId) === normalizedConversationId ) { setMessages(event.payload.messages); if (!isUnmounted) { setIsLoading(false); } } }); // 先读取内存快照,确保页面切换时无闪烁 const cachedMessages = messageManager.getMessages(normalizedConversationId); if (cachedMessages.length > 0) { setMessages(cachedMessages); } // 架构入口:激活会话并完成初始化/同步 messageManager.activateConversation(normalizedConversationId).catch(error => { console.error('[useMessages] 激活会话失败:', error); if (!isUnmounted) { setIsLoading(false); } }).finally(() => { if (!isUnmounted) { setIsLoading(false); } }); return () => { isUnmounted = true; unsubscribe(); if (String(messageManager.getActiveConversation()) === normalizedConversationId) { messageManager.setActiveConversation(null); } }; }, [conversationId]); const loadMore = useCallback(async () => { if (!conversationId || isLoading || !hasMore) return; const currentMessages = messageManager.getMessages(conversationId); if (currentMessages.length === 0) return; // 获取最早的消息seq const minSeq = Math.min(...currentMessages.map(m => m.seq)); setIsLoading(true); const loadedMessages = await messageManager.loadMoreMessages(conversationId, minSeq, 20); setIsLoading(false); // 如果没有加载到新消息,说明没有更多了 if (loadedMessages.length === 0) { setHasMore(false); } }, [conversationId, isLoading, hasMore]); const refresh = useCallback(async () => { if (!conversationId) return; setIsLoading(true); await messageManager.fetchMessages(conversationId); setMessages(messageManager.getMessages(conversationId)); setIsLoading(false); setHasMore(true); }, [conversationId]); return { messages, isLoading, hasMore, loadMore, refresh, }; } // ==================== useSendMessage - 发送消息 ==================== interface UseSendMessageReturn { sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise; isSending: boolean; } /** * 发送消息 * 返回发送函数和发送状态 */ export function useSendMessage(conversationId: string | null): UseSendMessageReturn { const [isSending, setIsSending] = useState(false); const sendMessage = useCallback( async (segments: MessageSegment[], options?: { replyToId?: string }): Promise => { if (!conversationId) return null; setIsSending(true); try { const message = await messageManager.sendMessage(conversationId, segments, options); return message; } finally { setIsSending(false); } }, [conversationId] ); return { sendMessage, isSending, }; } // ==================== useMarkAsRead - 标记已读 ==================== interface UseMarkAsReadReturn { markAsRead: (seq: number) => Promise; markAllAsRead: () => Promise; isMarking: boolean; } /** * 标记已读 * 用于ChatScreen标记当前会话已读 */ export function useMarkAsRead(conversationId: string | null): UseMarkAsReadReturn { const [isMarking, setIsMarking] = useState(false); const markAsRead = useCallback( async (seq: number) => { if (!conversationId) return; setIsMarking(true); try { await messageManager.markAsRead(conversationId, seq); } finally { setIsMarking(false); } }, [conversationId] ); const markAllAsRead = useCallback(async () => { setIsMarking(true); try { await messageManager.markAllAsRead(); } finally { setIsMarking(false); } }, []); return { markAsRead, markAllAsRead, isMarking, }; } // ==================== useUnreadCount - 获取未读数 ==================== interface UseUnreadCountReturn { totalUnreadCount: number; systemUnreadCount: number; } /** * 获取未读消息数 * 用于TabBar角标等 */ export function useUnreadCount(): UseUnreadCountReturn { const [counts, setCounts] = useState(() => messageManager.getUnreadCount()); useEffect(() => { const unsubscribe = messageManager.subscribe((event: MessageEvent) => { if (event.type === 'unread_count_updated') { setCounts(messageManager.getUnreadCount()); } }); // 初始化时获取最新未读数 messageManager.fetchUnreadCount(); return unsubscribe; }, []); return { totalUnreadCount: counts.total, systemUnreadCount: counts.system, }; } // ==================== useTotalUnreadCount - 获取总未读数(包含系统消息) ==================== /** * 获取总未读消息数(会话未读 + 系统消息未读) * 专用于TabBar徽章显示 */ export function useTotalUnreadCount(): number { const [totalCount, setTotalCount] = useState(() => { const counts = messageManager.getUnreadCount(); return counts.total + counts.system; }); useEffect(() => { const unsubscribe = messageManager.subscribe((event: MessageEvent) => { if (event.type === 'unread_count_updated') { const counts = messageManager.getUnreadCount(); setTotalCount(counts.total + counts.system); } }); // 初始化时获取最新未读数 messageManager.fetchUnreadCount(); return unsubscribe; }, []); return totalCount; } // ==================== useConversation - 获取单个会话信息 ==================== interface UseConversationReturn { conversation: ConversationResponse | null; isLoading: boolean; refresh: () => Promise; } /** * 获取单个会话信息 */ export function useConversation(conversationId: string | null): UseConversationReturn { const [conversation, setConversation] = useState(null); const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (!conversationId) { setConversation(null); return; } // 获取当前会话信息 setConversation(messageManager.getConversation(conversationId)); // 订阅更新 const unsubscribe = messageManager.subscribe((event: MessageEvent) => { if (event.type === 'conversations_updated') { const updated = messageManager.getConversation(conversationId); setConversation(updated); } }); return unsubscribe; }, [conversationId]); const refresh = useCallback(async () => { if (!conversationId) return; setIsLoading(true); await messageManager.fetchConversationDetail(conversationId); setConversation(messageManager.getConversation(conversationId)); setIsLoading(false); }, [conversationId]); return { conversation, isLoading, refresh, }; } // ==================== useMessageManager - 通用订阅 ==================== interface UseMessageManagerReturn { isConnected: boolean; isInitialized: boolean; } /** * 通用MessageManager订阅 * 用于获取连接状态等全局信息 */ export function useMessageManager(): UseMessageManagerReturn { const [isConnected, setIsConnected] = useState(() => messageManager.isConnected()); const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { const unsubscribe = messageManager.subscribe((event: MessageEvent) => { if (event.type === 'connection_changed') { setIsConnected(event.payload.connected); } }); // 初始化 messageManager.initialize().then(() => { setIsInitialized(true); }); return unsubscribe; }, []); return { isConnected, isInitialized, }; } // ==================== useCreateConversation - 创建会话 ==================== interface UseCreateConversationReturn { createConversation: (userId: string) => Promise; isCreating: boolean; } /** * 创建私聊会话 */ export function useCreateConversation(): UseCreateConversationReturn { const [isCreating, setIsCreating] = useState(false); const createConversation = useCallback(async (userId: string): Promise => { setIsCreating(true); try { const conversation = await messageManager.createConversation(userId); return conversation; } finally { setIsCreating(false); } }, []); return { createConversation, isCreating, }; } // ==================== useUpdateConversation - 更新会话 ==================== interface UseUpdateConversationReturn { updateConversation: (conversationId: string, updates: Partial) => void; } /** * 本地更新会话信息 */ export function useUpdateConversation(): UseUpdateConversationReturn { const updateConversation = useCallback((conversationId: string, updates: Partial) => { messageManager.updateConversation(conversationId, updates); }, []); return { updateConversation, }; } // ==================== useSystemUnreadCount - 系统消息未读数操作 ==================== interface UseSystemUnreadCountReturn { setSystemUnreadCount: (count: number) => void; incrementSystemUnreadCount: () => void; decrementSystemUnreadCount: (count?: number) => void; } /** * 系统消息未读数操作 */ export function useSystemUnreadCount(): UseSystemUnreadCountReturn { const setSystemUnreadCount = useCallback((count: number) => { messageManager.setSystemUnreadCount(count); }, []); const incrementSystemUnreadCount = useCallback(() => { messageManager.incrementSystemUnreadCount(); }, []); const decrementSystemUnreadCount = useCallback((count?: number) => { messageManager.decrementSystemUnreadCount(count); }, []); return { setSystemUnreadCount, incrementSystemUnreadCount, decrementSystemUnreadCount, }; } // ==================== useMessageListRefresh - MessageListScreen焦点刷新 ==================== interface UseMessageListRefreshReturn { refresh: () => Promise; isRefreshing: boolean; } /** * MessageListScreen焦点刷新Hook * 从 ChatScreen 返回时,MessageManager 内存状态已经是最新的(markAsRead 乐观更新), * 无需再请求服务器(避免时序竞争覆盖已读状态)。 * 仅在用户主动下拉刷新时才触发服务器请求。 * 注意:isFocused 参数已移除,不再触发焦点刷新 */ export function useMessageListRefresh(): UseMessageListRefreshReturn { const [isRefreshing, setIsRefreshing] = useState(false); const lastRefreshTimeRef = useRef(0); const refresh = useCallback(async () => { // 防止重复刷新(最小间隔500ms) const now = Date.now(); if (now - lastRefreshTimeRef.current < 500) { return; } lastRefreshTimeRef.current = now; setIsRefreshing(true); try { await messageManager.fetchConversations(true); } catch (error) { console.error('[useMessageListRefresh] 刷新失败:', error); } finally { setIsRefreshing(false); } }, []); // 获得焦点时不主动拉服务器——内存状态已是最新,避免时序竞争 // refresh 仅供下拉刷新手动调用 return { refresh, isRefreshing, }; } // ==================== 便捷hooks组合 ==================== /** * ChatScreen专用Hook组合 * 整合所有ChatScreen需要的功能 */ interface UseChatReturn { // 消息相关 messages: MessageResponse[]; isLoadingMessages: boolean; hasMoreMessages: boolean; loadMoreMessages: () => Promise; refreshMessages: () => Promise; // 发送消息 sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise; isSending: boolean; // 已读相关 markAsRead: (seq: number) => Promise; // 会话信息 conversation: ConversationResponse | null; } export function useChat(conversationId: string | null): UseChatReturn { const { messages, isLoading: isLoadingMessages, hasMore: hasMoreMessages, loadMore, refresh } = useMessages(conversationId); const { sendMessage, isSending } = useSendMessage(conversationId); const { markAsRead } = useMarkAsRead(conversationId); const { conversation } = useConversation(conversationId); return { messages, isLoadingMessages, hasMoreMessages, loadMoreMessages: loadMore, refreshMessages: refresh, sendMessage, isSending, markAsRead, conversation, }; } // ==================== useGroupTyping - 群聊输入状态 ==================== interface UseGroupTypingReturn { typingUsers: string[]; } /** * 获取群聊输入状态 * @param groupId 群组ID */ export function useGroupTyping(groupId: string | null): UseGroupTypingReturn { const [typingUsers, setTypingUsers] = useState([]); useEffect(() => { if (!groupId) { setTypingUsers([]); return; } // 初始值 setTypingUsers(messageManager.getTypingUsers(groupId)); // 订阅输入状态变化 const unsubscribe = messageManager.subscribe((event: MessageEvent) => { if (event.type === 'typing_status' && event.payload.groupId === groupId) { setTypingUsers(event.payload.typingUsers); } }); return unsubscribe; }, [groupId]); return { typingUsers }; } // ==================== useGroupMuted - 群聊禁言状态 ==================== interface UseGroupMutedReturn { isMuted: boolean; setMuted: (muted: boolean) => void; } /** * 获取群聊禁言状态 * @param groupId 群组ID * @param currentUserId 当前用户ID(用于判断禁言通知是否针对当前用户) */ export function useGroupMuted(groupId: string | null, currentUserId?: string): UseGroupMutedReturn { const [isMuted, setIsMuted] = useState(false); useEffect(() => { if (!groupId) { setIsMuted(false); return; } // 初始值 setIsMuted(messageManager.isMuted(groupId)); // 订阅群通知变化 const unsubscribe = messageManager.subscribe((event: MessageEvent) => { if (event.type === 'group_notice' && event.payload.groupId === groupId) { const { noticeType, data } = event.payload; // 如果提供了当前用户ID,只响应当前用户的禁言/解禁通知 if ((noticeType === 'muted' || noticeType === 'unmuted')) { if (!currentUserId || data?.user_id === currentUserId) { setIsMuted(noticeType === 'muted'); } } } }); return unsubscribe; }, [groupId, currentUserId]); const setMuted = useCallback((muted: boolean) => { if (groupId) { messageManager.setMutedStatus(groupId, muted); setIsMuted(muted); } }, [groupId]); return { isMuted, setMuted }; } /** * MessageListScreen专用Hook组合 */ interface UseMessageListReturn { conversations: ConversationResponse[]; isLoading: boolean; refresh: () => Promise; totalUnreadCount: number; systemUnreadCount: number; markAllAsRead: () => Promise; isMarking: boolean; } export function useMessageList(): UseMessageListReturn { const { conversations, isLoading, refresh } = useConversations(); const { totalUnreadCount, systemUnreadCount } = useUnreadCount(); const { markAllAsRead, isMarking } = useMarkAsRead(null); return { conversations, isLoading, refresh, totalUnreadCount, systemUnreadCount, markAllAsRead, isMarking, }; }