2026-03-31 18:22:24 +08:00
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* MessageManager React Hooks
|
|
|
|
|
|
*
|
|
|
|
|
|
* 提供React组件与MessageManager的集成
|
|
|
|
|
|
* 所有hooks都基于Zustand selector机制
|
2026-03-31 18:22:24 +08:00
|
|
|
|
* 确保组件能实时获取状态更新
|
|
|
|
|
|
*
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 重构说明:
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* - 移除了手动订阅机制,改用 Zustand selector
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* - Zustand 会自动处理依赖追踪和组件重渲染
|
|
|
|
|
|
* - 不需要手动管理订阅/取消订阅
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
2026-03-31 19:15:13 +08:00
|
|
|
|
import { useShallow } from 'zustand/react/shallow';
|
2026-03-31 18:22:24 +08:00
|
|
|
|
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
2026-04-13 04:56:58 +08:00
|
|
|
|
import { messageManager } from './MessageManager';
|
|
|
|
|
|
import { useMessageStore } from './store';
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== useConversations - 获取会话列表 ====================
|
|
|
|
|
|
|
|
|
|
|
|
interface UseConversationsReturn {
|
|
|
|
|
|
conversations: ConversationResponse[];
|
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
|
refresh: () => Promise<void>;
|
|
|
|
|
|
loadMore: () => Promise<void>;
|
|
|
|
|
|
hasMore: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取会话列表
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 自动订阅状态变化
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useConversations(): UseConversationsReturn {
|
2026-03-31 19:15:13 +08:00
|
|
|
|
// 使用 Zustand selector 直接订阅状态
|
|
|
|
|
|
const conversations = useMessageStore(state => state.conversationList);
|
|
|
|
|
|
const isLoading = useMessageStore(state => state.isLoadingConversations);
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const [hasMore, setHasMore] = useState(() => messageManager.canLoadMoreConversations());
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// 初始化时确保 MessageManager 已初始化
|
|
|
|
|
|
messageManager.initialize().catch(error => {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
console.error('[useConversations] 初始化失败:', error);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// 冷启动兜底:延迟做一次强制刷新,避免首次因时序问题拿到空列表
|
2026-03-31 18:22:24 +08:00
|
|
|
|
const coldStartSyncTimer = setTimeout(() => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
messageManager.refreshConversations(true, 'hooks-initial-refresh').catch(error => {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
console.error('[useConversations] 冷启动强制刷新失败:', error);
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 1200);
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
clearTimeout(coldStartSyncTimer);
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-03-31 19:15:13 +08:00
|
|
|
|
// 监听 hasMore 变化
|
|
|
|
|
|
useEffect(() => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
setHasMore(messageManager.canLoadMoreConversations());
|
|
|
|
|
|
}, [conversations]);
|
2026-03-31 19:15:13 +08:00
|
|
|
|
|
2026-03-31 18:22:24 +08:00
|
|
|
|
const refresh = useCallback(async () => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
await messageManager.refreshConversations(true, 'hooks-manual-refresh');
|
|
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
const loadMore = useCallback(async () => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
await messageManager.loadMoreConversations();
|
|
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
conversations,
|
|
|
|
|
|
isLoading,
|
|
|
|
|
|
refresh,
|
|
|
|
|
|
loadMore,
|
|
|
|
|
|
hasMore,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== useMessages - 获取指定会话的消息 ====================
|
|
|
|
|
|
|
|
|
|
|
|
interface UseMessagesReturn {
|
|
|
|
|
|
messages: MessageResponse[];
|
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
|
hasMore: boolean;
|
|
|
|
|
|
loadMore: () => Promise<void>;
|
|
|
|
|
|
refresh: () => Promise<void>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取指定会话的消息
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 自动订阅消息变化
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useMessages(conversationId: string | null): UseMessagesReturn {
|
|
|
|
|
|
const normalizedConversationId = conversationId ? String(conversationId) : null;
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-03-31 19:15:13 +08:00
|
|
|
|
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
|
|
|
|
|
const messages = useMessageStore(
|
2026-04-13 04:56:58 +08:00
|
|
|
|
useShallow(state =>
|
|
|
|
|
|
normalizedConversationId ? (state.messagesMap.get(normalizedConversationId) || []) : []
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
const isLoadingMessages = useMessageStore(state =>
|
|
|
|
|
|
normalizedConversationId ? state.loadingMessagesSet.has(normalizedConversationId) : false
|
2026-03-31 18:22:24 +08:00
|
|
|
|
);
|
|
|
|
|
|
const [hasMore, setHasMore] = useState(true);
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const loadMoreInFlightRef = useRef(false);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
if (!normalizedConversationId) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 架构入口:激活会话并完成初始化/同步
|
|
|
|
|
|
messageManager.activateConversation(normalizedConversationId).catch(error => {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
console.error('[useMessages] 激活会话失败:', error);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
// 清理活动会话
|
2026-04-13 04:56:58 +08:00
|
|
|
|
if (messageManager.getActiveConversation() === normalizedConversationId) {
|
|
|
|
|
|
messageManager.setActiveConversation(null);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [normalizedConversationId]);
|
|
|
|
|
|
|
|
|
|
|
|
const loadMore = useCallback(async () => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
if (!conversationId || !hasMore || loadMoreInFlightRef.current) return;
|
|
|
|
|
|
|
|
|
|
|
|
const currentMessages = messageManager.getMessages(conversationId);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
if (currentMessages.length === 0) return;
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// 消息数组在 MessageManager 内保持 seq 升序,首项即最早消息
|
|
|
|
|
|
const minSeq = currentMessages[0]?.seq ?? Math.min(...currentMessages.map(m => m.seq));
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
loadMoreInFlightRef.current = true;
|
|
|
|
|
|
const loadedMessages = await messageManager.loadMoreMessages(conversationId, minSeq, 20);
|
|
|
|
|
|
loadMoreInFlightRef.current = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有加载到新消息,说明没有更多了
|
|
|
|
|
|
if (loadedMessages.length === 0) {
|
|
|
|
|
|
setHasMore(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [conversationId, hasMore]);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
const refresh = useCallback(async () => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
if (!conversationId) return;
|
|
|
|
|
|
|
|
|
|
|
|
await messageManager.fetchMessages(conversationId);
|
|
|
|
|
|
setHasMore(true);
|
|
|
|
|
|
}, [conversationId]);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
messages,
|
2026-03-31 19:15:13 +08:00
|
|
|
|
isLoading: isLoadingMessages,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
hasMore,
|
|
|
|
|
|
loadMore,
|
|
|
|
|
|
refresh,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== useSendMessage - 发送消息 ====================
|
|
|
|
|
|
|
|
|
|
|
|
interface UseSendMessageReturn {
|
|
|
|
|
|
sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>;
|
|
|
|
|
|
isSending: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发送消息
|
|
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useSendMessage(conversationId: string | null): UseSendMessageReturn {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
const [isSending, setIsSending] = useState(false);
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const sendMessage = useCallback(
|
|
|
|
|
|
async (segments: MessageSegment[], options?: { replyToId?: string }): Promise<MessageResponse | null> => {
|
|
|
|
|
|
if (!conversationId) return null;
|
|
|
|
|
|
|
|
|
|
|
|
setIsSending(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const message = await messageManager.sendMessage(conversationId, segments, options);
|
|
|
|
|
|
return message;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsSending(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
[conversationId]
|
|
|
|
|
|
);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
sendMessage,
|
|
|
|
|
|
isSending,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== useMarkAsRead - 标记已读 ====================
|
|
|
|
|
|
|
|
|
|
|
|
interface UseMarkAsReadReturn {
|
|
|
|
|
|
markAsRead: (seq: number) => Promise<void>;
|
|
|
|
|
|
markAllAsRead: () => Promise<void>;
|
2026-04-13 04:56:58 +08:00
|
|
|
|
isMarking: boolean;
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 标记已读
|
|
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
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]
|
|
|
|
|
|
);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
const markAllAsRead = useCallback(async () => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
setIsMarking(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await messageManager.markAllAsRead();
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsMarking(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
markAsRead,
|
|
|
|
|
|
markAllAsRead,
|
2026-04-13 04:56:58 +08:00
|
|
|
|
isMarking,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== useUnreadCount - 获取未读数 ====================
|
|
|
|
|
|
|
|
|
|
|
|
interface UseUnreadCountReturn {
|
|
|
|
|
|
totalUnreadCount: number;
|
|
|
|
|
|
systemUnreadCount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 获取未读消息数
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 直接订阅未读数变化
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useUnreadCount(): UseUnreadCountReturn {
|
2026-03-31 19:15:13 +08:00
|
|
|
|
// 使用 Zustand selector 直接订阅未读数
|
|
|
|
|
|
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
|
|
|
|
|
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// 初始化时获取最新未读数
|
|
|
|
|
|
messageManager.fetchUnreadCount();
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
2026-03-31 19:15:13 +08:00
|
|
|
|
totalUnreadCount,
|
|
|
|
|
|
systemUnreadCount,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// ==================== useTotalUnreadCount - 获取总未读数(包含系统消息) ====================
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 获取总未读消息数(会话未读 + 系统消息未读)
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 直接订阅
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useTotalUnreadCount(): number {
|
|
|
|
|
|
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
2026-03-31 19:15:13 +08:00
|
|
|
|
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
// 初始化时获取最新未读数
|
|
|
|
|
|
messageManager.fetchUnreadCount();
|
|
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
return totalUnreadCount + systemUnreadCount;
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// ==================== useConversation - 获取单个会话信息 ====================
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
interface UseConversationReturn {
|
|
|
|
|
|
conversation: ConversationResponse | null;
|
2026-04-13 04:56:58 +08:00
|
|
|
|
isLoading: boolean;
|
2026-03-31 18:22:24 +08:00
|
|
|
|
refresh: () => Promise<void>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 获取单个会话信息
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 直接订阅会话变化
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useConversation(conversationId: string | null): UseConversationReturn {
|
2026-03-31 19:15:13 +08:00
|
|
|
|
// 使用 useShallow 避免每次返回新对象引用导致的无限循环
|
|
|
|
|
|
const conversation = useMessageStore(
|
2026-04-13 04:56:58 +08:00
|
|
|
|
useShallow(state =>
|
|
|
|
|
|
conversationId ? (state.conversations.get(conversationId) || null) : null
|
|
|
|
|
|
)
|
2026-03-31 18:22:24 +08:00
|
|
|
|
);
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
const refresh = useCallback(async () => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
if (!conversationId) return;
|
|
|
|
|
|
|
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
|
await messageManager.fetchConversationDetail(conversationId);
|
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
|
}, [conversationId]);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
conversation,
|
2026-04-13 04:56:58 +08:00
|
|
|
|
isLoading,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
refresh,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 19:15:13 +08:00
|
|
|
|
// ==================== useMessageManager - 通用状态 ====================
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
interface UseMessageManagerReturn {
|
|
|
|
|
|
isConnected: boolean;
|
2026-04-13 04:56:58 +08:00
|
|
|
|
isInitialized: boolean;
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 通用MessageManager状态
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 直接订阅连接状态
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useMessageManager(): UseMessageManagerReturn {
|
|
|
|
|
|
// 使用 Zustand selector 直接订阅状态
|
2026-03-31 19:15:13 +08:00
|
|
|
|
const isConnected = useMessageStore(state => state.isWSConnected);
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const isInitialized = useMessageStore(state => state.isInitialized);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// 初始化
|
|
|
|
|
|
messageManager.initialize().catch(error => {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
console.error('[useMessageManager] 初始化失败:', error);
|
|
|
|
|
|
});
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
isConnected,
|
2026-04-13 04:56:58 +08:00
|
|
|
|
isInitialized,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== useCreateConversation - 创建会话 ====================
|
|
|
|
|
|
|
|
|
|
|
|
interface UseCreateConversationReturn {
|
|
|
|
|
|
createConversation: (userId: string) => Promise<ConversationResponse | null>;
|
|
|
|
|
|
isCreating: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 创建私聊会话
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useCreateConversation(): UseCreateConversationReturn {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const createConversation = useCallback(async (userId: string): Promise<ConversationResponse | null> => {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
setIsCreating(true);
|
|
|
|
|
|
try {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const conversation = await messageManager.createConversation(userId);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
return conversation;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
|
}
|
2026-04-13 04:56:58 +08:00
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
createConversation,
|
|
|
|
|
|
isCreating,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== useUpdateConversation - 更新会话 ====================
|
|
|
|
|
|
|
|
|
|
|
|
interface UseUpdateConversationReturn {
|
|
|
|
|
|
updateConversation: (conversationId: string, updates: Partial<ConversationResponse>) => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 本地更新会话信息
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useUpdateConversation(): UseUpdateConversationReturn {
|
2026-03-31 18:22:24 +08:00
|
|
|
|
const updateConversation = useCallback((conversationId: string, updates: Partial<ConversationResponse>) => {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
messageManager.updateConversation(conversationId, updates);
|
|
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
updateConversation,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// ==================== useSystemUnreadCount - 系统消息未读数操作 ====================
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
interface UseSystemUnreadCountReturn {
|
|
|
|
|
|
setSystemUnreadCount: (count: number) => void;
|
|
|
|
|
|
incrementSystemUnreadCount: () => void;
|
|
|
|
|
|
decrementSystemUnreadCount: (count?: number) => void;
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 系统消息未读数操作
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
setSystemUnreadCount,
|
|
|
|
|
|
incrementSystemUnreadCount,
|
|
|
|
|
|
decrementSystemUnreadCount,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// ==================== useMessageListRefresh - MessageListScreen焦点刷新 ====================
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
interface UseMessageListRefreshReturn {
|
|
|
|
|
|
refresh: () => Promise<void>;
|
|
|
|
|
|
isRefreshing: boolean;
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* MessageListScreen焦点刷新Hook
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useMessageListRefresh(): UseMessageListRefreshReturn {
|
|
|
|
|
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
|
|
|
|
const lastRefreshTimeRef = useRef<number>(0);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
const refresh = useCallback(async () => {
|
|
|
|
|
|
// 防止重复刷新(最小间隔500ms)
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
if (now - lastRefreshTimeRef.current < 500) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
lastRefreshTimeRef.current = now;
|
|
|
|
|
|
|
|
|
|
|
|
setIsRefreshing(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await messageManager.refreshConversations(true, 'hooks-load-more-fallback');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[useMessageListRefresh] 刷新失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsRefreshing(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
refresh,
|
|
|
|
|
|
isRefreshing,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// ==================== 便捷hooks组合 ====================
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* ChatScreen专用Hook组合
|
|
|
|
|
|
*/
|
|
|
|
|
|
interface UseChatReturn {
|
|
|
|
|
|
// 消息相关
|
|
|
|
|
|
messages: MessageResponse[];
|
|
|
|
|
|
isLoadingMessages: boolean;
|
|
|
|
|
|
hasMoreMessages: boolean;
|
|
|
|
|
|
loadMoreMessages: () => Promise<void>;
|
|
|
|
|
|
refreshMessages: () => Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
// 发送消息
|
|
|
|
|
|
sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>;
|
|
|
|
|
|
isSending: boolean;
|
|
|
|
|
|
|
|
|
|
|
|
// 已读相关
|
|
|
|
|
|
markAsRead: (seq: number) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
// 会话信息
|
|
|
|
|
|
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[];
|
2026-03-31 18:22:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 获取群聊输入状态
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 直接订阅
|
2026-03-31 18:22:24 +08:00
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useGroupTyping(groupId: string | null): UseGroupTypingReturn {
|
|
|
|
|
|
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
|
|
|
|
|
const typingUsers = useMessageStore(
|
|
|
|
|
|
useShallow(state =>
|
|
|
|
|
|
groupId ? (state.typingUsersMap.get(groupId) || []) : []
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
return { typingUsers };
|
2026-03-31 19:15:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
// ==================== useGroupMuted - 群聊禁言状态 ====================
|
2026-03-31 19:15:13 +08:00
|
|
|
|
|
2026-04-13 04:56:58 +08:00
|
|
|
|
interface UseGroupMutedReturn {
|
|
|
|
|
|
isMuted: boolean;
|
|
|
|
|
|
setMuted: (muted: boolean) => void;
|
2026-03-31 19:15:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-13 04:56:58 +08:00
|
|
|
|
* 获取群聊禁言状态
|
2026-03-31 19:15:13 +08:00
|
|
|
|
* 使用 Zustand selector 直接订阅
|
|
|
|
|
|
*/
|
2026-04-13 04:56:58 +08:00
|
|
|
|
export function useGroupMuted(groupId: string | null, currentUserId?: string): UseGroupMutedReturn {
|
|
|
|
|
|
// 使用 Zustand selector 直接订阅禁言状态
|
|
|
|
|
|
const isMuted = useMessageStore(state =>
|
|
|
|
|
|
groupId ? (state.mutedStatusMap.get(groupId) || false) : false
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const setMuted = useCallback((muted: boolean) => {
|
|
|
|
|
|
if (groupId) {
|
|
|
|
|
|
messageManager.setMutedStatus(groupId, muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [groupId]);
|
|
|
|
|
|
|
|
|
|
|
|
return { isMuted, setMuted };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* MessageListScreen专用Hook组合
|
|
|
|
|
|
*/
|
|
|
|
|
|
interface UseMessageListReturn {
|
|
|
|
|
|
conversations: ConversationResponse[];
|
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
|
refresh: () => Promise<void>;
|
|
|
|
|
|
loadMore: () => Promise<void>;
|
|
|
|
|
|
hasMore: boolean;
|
|
|
|
|
|
totalUnreadCount: number;
|
|
|
|
|
|
systemUnreadCount: number;
|
|
|
|
|
|
markAllAsRead: () => Promise<void>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function useMessageList(): UseMessageListReturn {
|
|
|
|
|
|
const { conversations, isLoading, refresh, loadMore, hasMore } = useConversations();
|
|
|
|
|
|
const { totalUnreadCount, systemUnreadCount } = useUnreadCount();
|
|
|
|
|
|
const { markAllAsRead } = useMarkAsRead(null);
|
2026-03-31 18:22:24 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
2026-04-13 04:56:58 +08:00
|
|
|
|
conversations,
|
|
|
|
|
|
isLoading,
|
|
|
|
|
|
refresh,
|
|
|
|
|
|
loadMore,
|
|
|
|
|
|
hasMore,
|
|
|
|
|
|
totalUnreadCount,
|
|
|
|
|
|
systemUnreadCount,
|
|
|
|
|
|
markAllAsRead,
|
2026-03-31 18:22:24 +08:00
|
|
|
|
};
|
2026-03-31 19:15:13 +08:00
|
|
|
|
}
|