refactor(stores): reorganize flat store files into modular directory structure
Migrate from flat file organization to modular directory structure under src/stores/: - auth/: authStore, registerStore, verificationStore, sessionStore - call/: callStore - settings/: chatSettingsStore, themeStore - ui/: homeTabBarVisibilityStore, homeTabPressStore - utils/: routePayloadCache - group/sources.ts, group/profileResolver.ts - message/sources.ts - post/sources.ts Update all import paths across components and screens to use new module paths. Maintain backward compatibility through deprecated re-export files for gradual migration.
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import type { ConversationResponse, MessageResponse, MessageSegment, UserDTO } from '../../types/dto';
|
||||
import { useAuthStore } from '../authStore';
|
||||
import { useAuthStore } from '../auth/authStore';
|
||||
import type { MessageManagerConversationListDeps } from './types';
|
||||
|
||||
// 导入服务
|
||||
|
||||
482
src/stores/message/baseHooks.ts
Normal file
482
src/stores/message/baseHooks.ts
Normal file
@@ -0,0 +1,482 @@
|
||||
/**
|
||||
* 消息模块 React Hooks
|
||||
*
|
||||
* 提供React组件与消息状态管理的集成
|
||||
* 所有hooks都基于zustand store的selector机制
|
||||
* 确保组件能实时获取状态更新
|
||||
*
|
||||
* 重构说明:
|
||||
* - 移除了 SubscriptionManager,改用 Zustand selector
|
||||
* - Zustand 会自动处理依赖追踪和组件重渲染
|
||||
* - 不需要手动管理订阅/取消订阅
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
||||
import { useMessageStore, normalizeConversationId } from './store';
|
||||
|
||||
// ==================== useConversations - 获取会话列表 ====================
|
||||
|
||||
interface UseConversationsReturn {
|
||||
conversations: ConversationResponse[];
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<void>;
|
||||
loadMore: () => Promise<void>;
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* 用于 MessageListScreen 等需要显示会话列表的组件
|
||||
* 使用 Zustand selector 自动订阅状态变化
|
||||
*/
|
||||
export function useConversations(
|
||||
fetchConversations: (forceRefresh: boolean, source: string) => Promise<void>,
|
||||
loadMoreConversations: () => Promise<void>,
|
||||
canLoadMore: () => boolean,
|
||||
initialize: () => Promise<void>
|
||||
): UseConversationsReturn {
|
||||
// 使用 Zustand selector 直接订阅状态
|
||||
const conversations = useMessageStore(state => state.conversationList);
|
||||
const isLoading = useMessageStore(state => state.isLoadingConversations);
|
||||
const [hasMore, setHasMore] = useState(() => canLoadMore());
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化
|
||||
initialize().catch(error => {
|
||||
console.error('[useConversations] 初始化失败:', error);
|
||||
});
|
||||
|
||||
// 冷启动兜底:延迟做一次强制刷新
|
||||
const coldStartSyncTimer = setTimeout(() => {
|
||||
fetchConversations(true, 'hooks-initial-refresh').catch(error => {
|
||||
console.error('[useConversations] 冷启动强制刷新失败:', error);
|
||||
});
|
||||
}, 1200);
|
||||
|
||||
return () => {
|
||||
clearTimeout(coldStartSyncTimer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 监听 hasMore 变化
|
||||
useEffect(() => {
|
||||
setHasMore(canLoadMore());
|
||||
}, [conversations, canLoadMore]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchConversations(true, 'hooks-manual-refresh');
|
||||
}, [fetchConversations]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
await loadMoreConversations();
|
||||
}, [loadMoreConversations]);
|
||||
|
||||
return {
|
||||
conversations,
|
||||
isLoading,
|
||||
refresh,
|
||||
loadMore,
|
||||
hasMore,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useMessages - 获取指定会话的消息 ====================
|
||||
|
||||
interface UseMessagesReturn {
|
||||
messages: MessageResponse[];
|
||||
isLoading: boolean;
|
||||
hasMore: boolean;
|
||||
loadMore: () => Promise<void>;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定会话的消息
|
||||
* 使用 Zustand selector 自动订阅消息变化
|
||||
*/
|
||||
export function useMessages(
|
||||
conversationId: string,
|
||||
fetchMessages: (conversationId: string) => Promise<void>,
|
||||
loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise<MessageResponse[]>
|
||||
): UseMessagesReturn {
|
||||
const normalizedConversationId = normalizeConversationId(conversationId);
|
||||
const store = useMessageStore();
|
||||
|
||||
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||
const messages = useMessageStore(
|
||||
useShallow(state => state.messagesMap.get(normalizedConversationId) || [])
|
||||
);
|
||||
const isLoadingMessages = useMessageStore(state => state.loadingMessagesSet.has(normalizedConversationId));
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// 激活会话
|
||||
fetchMessages(normalizedConversationId).catch(error => {
|
||||
console.error('[useMessages] 激活会话失败:', error);
|
||||
});
|
||||
|
||||
return () => {
|
||||
// 清理活动会话
|
||||
if (store.getActiveConversation() === normalizedConversationId) {
|
||||
store.setCurrentConversation(null);
|
||||
}
|
||||
};
|
||||
}, [normalizedConversationId]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
const currentMessages = messages;
|
||||
if (currentMessages.length === 0) return;
|
||||
|
||||
// 消息数组在 store 内保持 seq 升序,首项即最早消息
|
||||
const minSeq = currentMessages[0]?.seq;
|
||||
if (minSeq === undefined) return;
|
||||
|
||||
const loadedMessages = await loadMoreMessages(conversationId, minSeq, 20);
|
||||
setHasMore(loadedMessages.length > 0);
|
||||
}, [conversationId, messages, loadMoreMessages]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchMessages(conversationId);
|
||||
}, [conversationId, fetchMessages]);
|
||||
|
||||
return {
|
||||
messages,
|
||||
isLoading: isLoadingMessages,
|
||||
hasMore,
|
||||
loadMore,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useSendMessage - 发送消息 ====================
|
||||
|
||||
interface UseSendMessageReturn {
|
||||
sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>;
|
||||
isSending: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
export function useSendMessage(
|
||||
conversationId: string,
|
||||
sendMessageService: (conversationId: string, segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>
|
||||
): UseSendMessageReturn {
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
|
||||
const sendMessage = useCallback(async (segments: MessageSegment[], options?: { replyToId?: string }) => {
|
||||
setIsSending(true);
|
||||
try {
|
||||
const message = await sendMessageService(conversationId, segments, options);
|
||||
return message;
|
||||
} catch (error) {
|
||||
console.error('[useSendMessage] 发送消息失败:', error);
|
||||
return null;
|
||||
} finally {
|
||||
setIsSending(false);
|
||||
}
|
||||
}, [conversationId, sendMessageService]);
|
||||
|
||||
return {
|
||||
sendMessage,
|
||||
isSending,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useMarkAsRead - 标记已读 ====================
|
||||
|
||||
interface UseMarkAsReadReturn {
|
||||
markAsRead: (seq: number) => Promise<void>;
|
||||
markAllAsRead: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已读
|
||||
*/
|
||||
export function useMarkAsRead(
|
||||
conversationId: string,
|
||||
markAsReadService: (conversationId: string, seq: number) => Promise<void>,
|
||||
markAllAsReadService: () => Promise<void>
|
||||
): UseMarkAsReadReturn {
|
||||
const markAsRead = useCallback(async (seq: number) => {
|
||||
await markAsReadService(conversationId, seq);
|
||||
}, [conversationId, markAsReadService]);
|
||||
|
||||
const markAllAsRead = useCallback(async () => {
|
||||
await markAllAsReadService();
|
||||
}, [markAllAsReadService]);
|
||||
|
||||
return {
|
||||
markAsRead,
|
||||
markAllAsRead,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useUnreadCount - 获取未读数 ====================
|
||||
|
||||
interface UseUnreadCountReturn {
|
||||
totalUnreadCount: number;
|
||||
systemUnreadCount: number;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读数
|
||||
* 使用 Zustand selector 直接订阅未读数变化
|
||||
*/
|
||||
export function useUnreadCount(fetchUnreadCount: () => Promise<void>): UseUnreadCountReturn {
|
||||
// 使用 Zustand selector 直接订阅未读数
|
||||
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
||||
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||
|
||||
useEffect(() => {
|
||||
// 初始获取
|
||||
fetchUnreadCount();
|
||||
}, []);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchUnreadCount();
|
||||
}, [fetchUnreadCount]);
|
||||
|
||||
return {
|
||||
totalUnreadCount,
|
||||
systemUnreadCount,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useSystemUnreadCount - 获取系统消息未读数 ====================
|
||||
|
||||
interface UseSystemUnreadCountReturn {
|
||||
systemUnreadCount: number;
|
||||
setSystemUnreadCount: (count: number) => void;
|
||||
incrementSystemUnreadCount: () => void;
|
||||
decrementSystemUnreadCount: (count?: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统消息未读数
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useSystemUnreadCount(
|
||||
setSystemUnreadCountService: (count: number) => void,
|
||||
incrementSystemUnreadCountService: () => void,
|
||||
decrementSystemUnreadCountService: (count?: number) => void
|
||||
): UseSystemUnreadCountReturn {
|
||||
// 使用 Zustand selector 直接订阅
|
||||
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||
|
||||
const setSystemUnreadCount = useCallback((count: number) => {
|
||||
setSystemUnreadCountService(count);
|
||||
}, [setSystemUnreadCountService]);
|
||||
|
||||
const incrementSystemUnreadCount = useCallback(() => {
|
||||
incrementSystemUnreadCountService();
|
||||
}, [incrementSystemUnreadCountService]);
|
||||
|
||||
const decrementSystemUnreadCount = useCallback((count = 1) => {
|
||||
decrementSystemUnreadCountService(count);
|
||||
}, [decrementSystemUnreadCountService]);
|
||||
|
||||
return {
|
||||
systemUnreadCount,
|
||||
setSystemUnreadCount,
|
||||
incrementSystemUnreadCount,
|
||||
decrementSystemUnreadCount,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useConversation - 获取单个会话 ====================
|
||||
|
||||
interface UseConversationReturn {
|
||||
conversation: ConversationResponse | null;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个会话
|
||||
* 使用 Zustand selector 直接订阅会话变化
|
||||
*/
|
||||
export function useConversation(
|
||||
conversationId: string,
|
||||
fetchConversationDetail: (conversationId: string) => Promise<ConversationResponse | null>
|
||||
): UseConversationReturn {
|
||||
// 使用 useShallow 避免每次返回新对象引用导致的无限循环
|
||||
const conversation = useMessageStore(
|
||||
useShallow(state => state.conversations.get(conversationId) || null)
|
||||
);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchConversationDetail(conversationId);
|
||||
}, [conversationId, fetchConversationDetail]);
|
||||
|
||||
return {
|
||||
conversation,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useMessageManager - 通用状态 ====================
|
||||
|
||||
interface UseMessageManagerReturn {
|
||||
isConnected: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用消息管理状态
|
||||
* 使用 Zustand selector 直接订阅连接状态
|
||||
*/
|
||||
export function useMessageManager(initialize: () => Promise<void>): UseMessageManagerReturn {
|
||||
// 使用 Zustand selector 直接订阅连接状态
|
||||
const isConnected = useMessageStore(state => state.isWSConnected);
|
||||
|
||||
useEffect(() => {
|
||||
initialize().catch(error => {
|
||||
console.error('[useMessageManager] 初始化失败:', error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useCreateConversation - 创建会话 ====================
|
||||
|
||||
interface UseCreateConversationReturn {
|
||||
createConversation: (userId: string) => Promise<ConversationResponse | null>;
|
||||
isCreating: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建会话
|
||||
*/
|
||||
export function useCreateConversation(
|
||||
createConversationService: (userId: string) => Promise<ConversationResponse | null>
|
||||
): UseCreateConversationReturn {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const createConversation = useCallback(async (userId: string) => {
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const conversation = await createConversationService(userId);
|
||||
return conversation;
|
||||
} catch (error) {
|
||||
console.error('[useCreateConversation] 创建会话失败:', error);
|
||||
return null;
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
}, [createConversationService]);
|
||||
|
||||
return {
|
||||
createConversation,
|
||||
isCreating,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useUpdateConversation - 更新会话 ====================
|
||||
|
||||
interface UseUpdateConversationReturn {
|
||||
updateConversation: (conversationId: string, updates: Partial<ConversationResponse>) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会话
|
||||
*/
|
||||
export function useUpdateConversation(
|
||||
updateConversationService: (conversationId: string, updates: Partial<ConversationResponse>) => void
|
||||
): UseUpdateConversationReturn {
|
||||
const updateConversation = useCallback((conversationId: string, updates: Partial<ConversationResponse>) => {
|
||||
updateConversationService(conversationId, updates);
|
||||
}, [updateConversationService]);
|
||||
|
||||
return {
|
||||
updateConversation,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useGroupTyping - 群聊输入状态 ====================
|
||||
|
||||
interface UseGroupTypingReturn {
|
||||
typingUsers: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群聊输入状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useGroupTyping(groupId: string): UseGroupTypingReturn {
|
||||
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||
const typingUsers = useMessageStore(
|
||||
useShallow(state => state.typingUsersMap.get(groupId) || [])
|
||||
);
|
||||
|
||||
return {
|
||||
typingUsers,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useGroupMuted - 群聊禁言状态 ====================
|
||||
|
||||
interface UseGroupMutedReturn {
|
||||
isMuted: boolean;
|
||||
setMutedStatus: (muted: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群聊禁言状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useGroupMuted(groupId: string): UseGroupMutedReturn {
|
||||
// 使用 Zustand selector 直接订阅禁言状态
|
||||
const isMuted = useMessageStore(state => state.mutedStatusMap.get(groupId) || false);
|
||||
const store = useMessageStore();
|
||||
|
||||
const setMutedStatus = useCallback((muted: boolean) => {
|
||||
store.setMutedStatus(groupId, muted);
|
||||
}, [groupId, store]);
|
||||
|
||||
return {
|
||||
isMuted,
|
||||
setMutedStatus,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useConnectionStatus - 连接状态 ====================
|
||||
|
||||
interface UseConnectionStatusReturn {
|
||||
isConnected: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useConnectionStatus(): UseConnectionStatusReturn {
|
||||
const isConnected = useMessageStore(state => state.isWSConnected);
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useIsInitialized - 初始化状态 ====================
|
||||
|
||||
interface UseIsInitializedReturn {
|
||||
isInitialized: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取初始化状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useIsInitialized(): UseIsInitializedReturn {
|
||||
const isInitialized = useMessageStore(state => state.isInitialized);
|
||||
|
||||
return {
|
||||
isInitialized,
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* 消息模块 React Hooks
|
||||
*
|
||||
* 提供React组件与消息状态管理的集成
|
||||
* 所有hooks都基于zustand store的selector机制
|
||||
* MessageManager React Hooks
|
||||
*
|
||||
* 提供React组件与MessageManager的集成
|
||||
* 所有hooks都基于Zustand selector机制
|
||||
* 确保组件能实时获取状态更新
|
||||
*
|
||||
* 重构说明:
|
||||
* - 移除了 SubscriptionManager,改用 Zustand selector
|
||||
* - 移除了手动订阅机制,改用 Zustand selector
|
||||
* - Zustand 会自动处理依赖追踪和组件重渲染
|
||||
* - 不需要手动管理订阅/取消订阅
|
||||
*/
|
||||
@@ -14,7 +14,8 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
||||
import { useMessageStore, normalizeConversationId } from './store';
|
||||
import { messageManager } from './MessageManager';
|
||||
import { useMessageStore } from './store';
|
||||
|
||||
// ==================== useConversations - 获取会话列表 ====================
|
||||
|
||||
@@ -28,29 +29,23 @@ interface UseConversationsReturn {
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
* 用于 MessageListScreen 等需要显示会话列表的组件
|
||||
* 使用 Zustand selector 自动订阅状态变化
|
||||
*/
|
||||
export function useConversations(
|
||||
fetchConversations: (forceRefresh: boolean, source: string) => Promise<void>,
|
||||
loadMoreConversations: () => Promise<void>,
|
||||
canLoadMore: () => boolean,
|
||||
initialize: () => Promise<void>
|
||||
): UseConversationsReturn {
|
||||
export function useConversations(): UseConversationsReturn {
|
||||
// 使用 Zustand selector 直接订阅状态
|
||||
const conversations = useMessageStore(state => state.conversationList);
|
||||
const isLoading = useMessageStore(state => state.isLoadingConversations);
|
||||
const [hasMore, setHasMore] = useState(() => canLoadMore());
|
||||
const [hasMore, setHasMore] = useState(() => messageManager.canLoadMoreConversations());
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化
|
||||
initialize().catch(error => {
|
||||
// 初始化时确保 MessageManager 已初始化
|
||||
messageManager.initialize().catch(error => {
|
||||
console.error('[useConversations] 初始化失败:', error);
|
||||
});
|
||||
|
||||
// 冷启动兜底:延迟做一次强制刷新
|
||||
// 冷启动兜底:延迟做一次强制刷新,避免首次因时序问题拿到空列表
|
||||
const coldStartSyncTimer = setTimeout(() => {
|
||||
fetchConversations(true, 'hooks-initial-refresh').catch(error => {
|
||||
messageManager.refreshConversations(true, 'hooks-initial-refresh').catch(error => {
|
||||
console.error('[useConversations] 冷启动强制刷新失败:', error);
|
||||
});
|
||||
}, 1200);
|
||||
@@ -62,16 +57,16 @@ export function useConversations(
|
||||
|
||||
// 监听 hasMore 变化
|
||||
useEffect(() => {
|
||||
setHasMore(canLoadMore());
|
||||
}, [conversations, canLoadMore]);
|
||||
setHasMore(messageManager.canLoadMoreConversations());
|
||||
}, [conversations]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchConversations(true, 'hooks-manual-refresh');
|
||||
}, [fetchConversations]);
|
||||
await messageManager.refreshConversations(true, 'hooks-manual-refresh');
|
||||
}, []);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
await loadMoreConversations();
|
||||
}, [loadMoreConversations]);
|
||||
await messageManager.loadMoreConversations();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
conversations,
|
||||
@@ -96,50 +91,64 @@ interface UseMessagesReturn {
|
||||
* 获取指定会话的消息
|
||||
* 使用 Zustand selector 自动订阅消息变化
|
||||
*/
|
||||
export function useMessages(
|
||||
conversationId: string,
|
||||
fetchMessages: (conversationId: string) => Promise<void>,
|
||||
loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise<MessageResponse[]>
|
||||
): UseMessagesReturn {
|
||||
const normalizedConversationId = normalizeConversationId(conversationId);
|
||||
const store = useMessageStore();
|
||||
export function useMessages(conversationId: string | null): UseMessagesReturn {
|
||||
const normalizedConversationId = conversationId ? String(conversationId) : null;
|
||||
|
||||
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||
const messages = useMessageStore(
|
||||
useShallow(state => state.messagesMap.get(normalizedConversationId) || [])
|
||||
useShallow(state =>
|
||||
normalizedConversationId ? (state.messagesMap.get(normalizedConversationId) || []) : []
|
||||
)
|
||||
);
|
||||
const isLoadingMessages = useMessageStore(state =>
|
||||
normalizedConversationId ? state.loadingMessagesSet.has(normalizedConversationId) : false
|
||||
);
|
||||
const isLoadingMessages = useMessageStore(state => state.loadingMessagesSet.has(normalizedConversationId));
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const loadMoreInFlightRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// 激活会话
|
||||
fetchMessages(normalizedConversationId).catch(error => {
|
||||
if (!normalizedConversationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 架构入口:激活会话并完成初始化/同步
|
||||
messageManager.activateConversation(normalizedConversationId).catch(error => {
|
||||
console.error('[useMessages] 激活会话失败:', error);
|
||||
});
|
||||
|
||||
return () => {
|
||||
// 清理活动会话
|
||||
if (store.getActiveConversation() === normalizedConversationId) {
|
||||
store.setCurrentConversation(null);
|
||||
if (messageManager.getActiveConversation() === normalizedConversationId) {
|
||||
messageManager.setActiveConversation(null);
|
||||
}
|
||||
};
|
||||
}, [normalizedConversationId]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
const currentMessages = messages;
|
||||
if (!conversationId || !hasMore || loadMoreInFlightRef.current) return;
|
||||
|
||||
const currentMessages = messageManager.getMessages(conversationId);
|
||||
if (currentMessages.length === 0) return;
|
||||
|
||||
// 消息数组在 store 内保持 seq 升序,首项即最早消息
|
||||
const minSeq = currentMessages[0]?.seq;
|
||||
if (minSeq === undefined) return;
|
||||
// 消息数组在 MessageManager 内保持 seq 升序,首项即最早消息
|
||||
const minSeq = currentMessages[0]?.seq ?? Math.min(...currentMessages.map(m => m.seq));
|
||||
|
||||
const loadedMessages = await loadMoreMessages(conversationId, minSeq, 20);
|
||||
setHasMore(loadedMessages.length > 0);
|
||||
}, [conversationId, messages, loadMoreMessages]);
|
||||
loadMoreInFlightRef.current = true;
|
||||
const loadedMessages = await messageManager.loadMoreMessages(conversationId, minSeq, 20);
|
||||
loadMoreInFlightRef.current = false;
|
||||
|
||||
// 如果没有加载到新消息,说明没有更多了
|
||||
if (loadedMessages.length === 0) {
|
||||
setHasMore(false);
|
||||
}
|
||||
}, [conversationId, hasMore]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchMessages(conversationId);
|
||||
}, [conversationId, fetchMessages]);
|
||||
if (!conversationId) return;
|
||||
|
||||
await messageManager.fetchMessages(conversationId);
|
||||
setHasMore(true);
|
||||
}, [conversationId]);
|
||||
|
||||
return {
|
||||
messages,
|
||||
@@ -160,24 +169,23 @@ interface UseSendMessageReturn {
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
export function useSendMessage(
|
||||
conversationId: string,
|
||||
sendMessageService: (conversationId: string, segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>
|
||||
): UseSendMessageReturn {
|
||||
export function useSendMessage(conversationId: string | null): UseSendMessageReturn {
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
|
||||
const sendMessage = useCallback(async (segments: MessageSegment[], options?: { replyToId?: string }) => {
|
||||
setIsSending(true);
|
||||
try {
|
||||
const message = await sendMessageService(conversationId, segments, options);
|
||||
return message;
|
||||
} catch (error) {
|
||||
console.error('[useSendMessage] 发送消息失败:', error);
|
||||
return null;
|
||||
} finally {
|
||||
setIsSending(false);
|
||||
}
|
||||
}, [conversationId, sendMessageService]);
|
||||
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]
|
||||
);
|
||||
|
||||
return {
|
||||
sendMessage,
|
||||
@@ -190,27 +198,42 @@ export function useSendMessage(
|
||||
interface UseMarkAsReadReturn {
|
||||
markAsRead: (seq: number) => Promise<void>;
|
||||
markAllAsRead: () => Promise<void>;
|
||||
isMarking: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已读
|
||||
*/
|
||||
export function useMarkAsRead(
|
||||
conversationId: string,
|
||||
markAsReadService: (conversationId: string, seq: number) => Promise<void>,
|
||||
markAllAsReadService: () => Promise<void>
|
||||
): UseMarkAsReadReturn {
|
||||
const markAsRead = useCallback(async (seq: number) => {
|
||||
await markAsReadService(conversationId, seq);
|
||||
}, [conversationId, markAsReadService]);
|
||||
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 () => {
|
||||
await markAllAsReadService();
|
||||
}, [markAllAsReadService]);
|
||||
setIsMarking(true);
|
||||
try {
|
||||
await messageManager.markAllAsRead();
|
||||
} finally {
|
||||
setIsMarking(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
markAsRead,
|
||||
markAllAsRead,
|
||||
isMarking,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,101 +242,78 @@ export function useMarkAsRead(
|
||||
interface UseUnreadCountReturn {
|
||||
totalUnreadCount: number;
|
||||
systemUnreadCount: number;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读数
|
||||
* 获取未读消息数
|
||||
* 使用 Zustand selector 直接订阅未读数变化
|
||||
*/
|
||||
export function useUnreadCount(fetchUnreadCount: () => Promise<void>): UseUnreadCountReturn {
|
||||
export function useUnreadCount(): UseUnreadCountReturn {
|
||||
// 使用 Zustand selector 直接订阅未读数
|
||||
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
||||
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||
|
||||
useEffect(() => {
|
||||
// 初始获取
|
||||
fetchUnreadCount();
|
||||
// 初始化时获取最新未读数
|
||||
messageManager.fetchUnreadCount();
|
||||
}, []);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchUnreadCount();
|
||||
}, [fetchUnreadCount]);
|
||||
|
||||
return {
|
||||
totalUnreadCount,
|
||||
systemUnreadCount,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useSystemUnreadCount - 获取系统消息未读数 ====================
|
||||
|
||||
interface UseSystemUnreadCountReturn {
|
||||
systemUnreadCount: number;
|
||||
setSystemUnreadCount: (count: number) => void;
|
||||
incrementSystemUnreadCount: () => void;
|
||||
decrementSystemUnreadCount: (count?: number) => void;
|
||||
}
|
||||
// ==================== useTotalUnreadCount - 获取总未读数(包含系统消息) ====================
|
||||
|
||||
/**
|
||||
* 获取系统消息未读数
|
||||
* 获取总未读消息数(会话未读 + 系统消息未读)
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useSystemUnreadCount(
|
||||
setSystemUnreadCountService: (count: number) => void,
|
||||
incrementSystemUnreadCountService: () => void,
|
||||
decrementSystemUnreadCountService: (count?: number) => void
|
||||
): UseSystemUnreadCountReturn {
|
||||
// 使用 Zustand selector 直接订阅
|
||||
export function useTotalUnreadCount(): number {
|
||||
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
||||
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||
|
||||
const setSystemUnreadCount = useCallback((count: number) => {
|
||||
setSystemUnreadCountService(count);
|
||||
}, [setSystemUnreadCountService]);
|
||||
useEffect(() => {
|
||||
// 初始化时获取最新未读数
|
||||
messageManager.fetchUnreadCount();
|
||||
}, []);
|
||||
|
||||
const incrementSystemUnreadCount = useCallback(() => {
|
||||
incrementSystemUnreadCountService();
|
||||
}, [incrementSystemUnreadCountService]);
|
||||
|
||||
const decrementSystemUnreadCount = useCallback((count = 1) => {
|
||||
decrementSystemUnreadCountService(count);
|
||||
}, [decrementSystemUnreadCountService]);
|
||||
|
||||
return {
|
||||
systemUnreadCount,
|
||||
setSystemUnreadCount,
|
||||
incrementSystemUnreadCount,
|
||||
decrementSystemUnreadCount,
|
||||
};
|
||||
return totalUnreadCount + systemUnreadCount;
|
||||
}
|
||||
|
||||
// ==================== useConversation - 获取单个会话 ====================
|
||||
// ==================== useConversation - 获取单个会话信息 ====================
|
||||
|
||||
interface UseConversationReturn {
|
||||
conversation: ConversationResponse | null;
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个会话
|
||||
* 获取单个会话信息
|
||||
* 使用 Zustand selector 直接订阅会话变化
|
||||
*/
|
||||
export function useConversation(
|
||||
conversationId: string,
|
||||
fetchConversationDetail: (conversationId: string) => Promise<ConversationResponse | null>
|
||||
): UseConversationReturn {
|
||||
export function useConversation(conversationId: string | null): UseConversationReturn {
|
||||
// 使用 useShallow 避免每次返回新对象引用导致的无限循环
|
||||
const conversation = useMessageStore(
|
||||
useShallow(state => state.conversations.get(conversationId) || null)
|
||||
useShallow(state =>
|
||||
conversationId ? (state.conversations.get(conversationId) || null) : null
|
||||
)
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await fetchConversationDetail(conversationId);
|
||||
}, [conversationId, fetchConversationDetail]);
|
||||
if (!conversationId) return;
|
||||
|
||||
setIsLoading(true);
|
||||
await messageManager.fetchConversationDetail(conversationId);
|
||||
setIsLoading(false);
|
||||
}, [conversationId]);
|
||||
|
||||
return {
|
||||
conversation,
|
||||
isLoading,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
@@ -322,24 +322,28 @@ export function useConversation(
|
||||
|
||||
interface UseMessageManagerReturn {
|
||||
isConnected: boolean;
|
||||
isInitialized: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用消息管理状态
|
||||
* 通用MessageManager状态
|
||||
* 使用 Zustand selector 直接订阅连接状态
|
||||
*/
|
||||
export function useMessageManager(initialize: () => Promise<void>): UseMessageManagerReturn {
|
||||
// 使用 Zustand selector 直接订阅连接状态
|
||||
export function useMessageManager(): UseMessageManagerReturn {
|
||||
// 使用 Zustand selector 直接订阅状态
|
||||
const isConnected = useMessageStore(state => state.isWSConnected);
|
||||
const isInitialized = useMessageStore(state => state.isInitialized);
|
||||
|
||||
useEffect(() => {
|
||||
initialize().catch(error => {
|
||||
// 初始化
|
||||
messageManager.initialize().catch(error => {
|
||||
console.error('[useMessageManager] 初始化失败:', error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
isInitialized,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -351,25 +355,20 @@ interface UseCreateConversationReturn {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建会话
|
||||
* 创建私聊会话
|
||||
*/
|
||||
export function useCreateConversation(
|
||||
createConversationService: (userId: string) => Promise<ConversationResponse | null>
|
||||
): UseCreateConversationReturn {
|
||||
export function useCreateConversation(): UseCreateConversationReturn {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const createConversation = useCallback(async (userId: string) => {
|
||||
const createConversation = useCallback(async (userId: string): Promise<ConversationResponse | null> => {
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const conversation = await createConversationService(userId);
|
||||
const conversation = await messageManager.createConversation(userId);
|
||||
return conversation;
|
||||
} catch (error) {
|
||||
console.error('[useCreateConversation] 创建会话失败:', error);
|
||||
return null;
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
}, [createConversationService]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
createConversation,
|
||||
@@ -384,20 +383,130 @@ interface UseUpdateConversationReturn {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会话
|
||||
* 本地更新会话信息
|
||||
*/
|
||||
export function useUpdateConversation(
|
||||
updateConversationService: (conversationId: string, updates: Partial<ConversationResponse>) => void
|
||||
): UseUpdateConversationReturn {
|
||||
export function useUpdateConversation(): UseUpdateConversationReturn {
|
||||
const updateConversation = useCallback((conversationId: string, updates: Partial<ConversationResponse>) => {
|
||||
updateConversationService(conversationId, updates);
|
||||
}, [updateConversationService]);
|
||||
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<void>;
|
||||
isRefreshing: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* MessageListScreen焦点刷新Hook
|
||||
*/
|
||||
export function useMessageListRefresh(): UseMessageListRefreshReturn {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const lastRefreshTimeRef = useRef<number>(0);
|
||||
|
||||
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);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
refresh,
|
||||
isRefreshing,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 便捷hooks组合 ====================
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@@ -408,75 +517,70 @@ interface UseGroupTypingReturn {
|
||||
* 获取群聊输入状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useGroupTyping(groupId: string): UseGroupTypingReturn {
|
||||
export function useGroupTyping(groupId: string | null): UseGroupTypingReturn {
|
||||
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||
const typingUsers = useMessageStore(
|
||||
useShallow(state => state.typingUsersMap.get(groupId) || [])
|
||||
useShallow(state =>
|
||||
groupId ? (state.typingUsersMap.get(groupId) || []) : []
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
typingUsers,
|
||||
};
|
||||
return { typingUsers };
|
||||
}
|
||||
|
||||
// ==================== useGroupMuted - 群聊禁言状态 ====================
|
||||
|
||||
interface UseGroupMutedReturn {
|
||||
isMuted: boolean;
|
||||
setMutedStatus: (muted: boolean) => void;
|
||||
setMuted: (muted: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群聊禁言状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useGroupMuted(groupId: string): UseGroupMutedReturn {
|
||||
export function useGroupMuted(groupId: string | null, currentUserId?: string): UseGroupMutedReturn {
|
||||
// 使用 Zustand selector 直接订阅禁言状态
|
||||
const isMuted = useMessageStore(state => state.mutedStatusMap.get(groupId) || false);
|
||||
const store = useMessageStore();
|
||||
const isMuted = useMessageStore(state =>
|
||||
groupId ? (state.mutedStatusMap.get(groupId) || false) : false
|
||||
);
|
||||
|
||||
const setMutedStatus = useCallback((muted: boolean) => {
|
||||
store.setMutedStatus(groupId, muted);
|
||||
}, [groupId, store]);
|
||||
const setMuted = useCallback((muted: boolean) => {
|
||||
if (groupId) {
|
||||
messageManager.setMutedStatus(groupId, muted);
|
||||
}
|
||||
}, [groupId]);
|
||||
|
||||
return {
|
||||
isMuted,
|
||||
setMutedStatus,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useConnectionStatus - 连接状态 ====================
|
||||
|
||||
interface UseConnectionStatusReturn {
|
||||
isConnected: boolean;
|
||||
return { isMuted, setMuted };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
* MessageListScreen专用Hook组合
|
||||
*/
|
||||
export function useConnectionStatus(): UseConnectionStatusReturn {
|
||||
const isConnected = useMessageStore(state => state.isWSConnected);
|
||||
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);
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== useIsInitialized - 初始化状态 ====================
|
||||
|
||||
interface UseIsInitializedReturn {
|
||||
isInitialized: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取初始化状态
|
||||
* 使用 Zustand selector 直接订阅
|
||||
*/
|
||||
export function useIsInitialized(): UseIsInitializedReturn {
|
||||
const isInitialized = useMessageStore(state => state.isInitialized);
|
||||
|
||||
return {
|
||||
isInitialized,
|
||||
conversations,
|
||||
isLoading,
|
||||
refresh,
|
||||
loadMore,
|
||||
hasMore,
|
||||
totalUnreadCount,
|
||||
systemUnreadCount,
|
||||
markAllAsRead,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export {
|
||||
WSMessageHandler,
|
||||
} from './services';
|
||||
|
||||
// ==================== Hooks 导出 ====================
|
||||
// ==================== Hooks 导出(便捷版,使用 messageManager 单例) ====================
|
||||
export {
|
||||
// 会话相关
|
||||
useConversations,
|
||||
@@ -87,6 +87,7 @@ export {
|
||||
|
||||
// 未读数相关
|
||||
useUnreadCount,
|
||||
useTotalUnreadCount,
|
||||
useSystemUnreadCount,
|
||||
|
||||
// 群聊相关
|
||||
@@ -95,9 +96,21 @@ export {
|
||||
|
||||
// 通用
|
||||
useMessageManager,
|
||||
useConnectionStatus,
|
||||
useIsInitialized,
|
||||
useMessageListRefresh,
|
||||
useChat,
|
||||
useMessageList,
|
||||
} from './hooks';
|
||||
|
||||
// ==================== 数据源导出 ====================
|
||||
export {
|
||||
CONVERSATION_LIST_PAGE_SIZE,
|
||||
type ConversationListPage,
|
||||
type IConversationListPagedSource,
|
||||
type RemoteConversationListSourceKind,
|
||||
NetworkRemoteConversationListPagedSource,
|
||||
SqliteConversationListPagedSource,
|
||||
createRemoteConversationListSource,
|
||||
} from './sources';
|
||||
|
||||
// ==================== 默认导出 ====================
|
||||
export { default } from './MessageManager';
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
SqliteConversationListPagedSource,
|
||||
createRemoteConversationListSource,
|
||||
CONVERSATION_LIST_PAGE_SIZE,
|
||||
} from '../../conversationListSources';
|
||||
} from '../sources';
|
||||
import type { IMessageSyncService, MessageManagerConversationListDeps, IUserCacheService } from '../types';
|
||||
import { useMessageStore, normalizeConversationId, mergeMessagesById } from '../store';
|
||||
import { ReadReceiptManager } from './ReadReceiptManager';
|
||||
|
||||
114
src/stores/message/sources.ts
Normal file
114
src/stores/message/sources.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 会话列表数据源抽象:游标、常规页码、SQLite 等实现同一契约,MessageManager 只依赖接口。
|
||||
*/
|
||||
|
||||
import { ConversationResponse } from '../../types/dto';
|
||||
import { messageService } from '@/services/message';
|
||||
import { conversationRepository } from '@/database';
|
||||
|
||||
export const CONVERSATION_LIST_PAGE_SIZE = 20;
|
||||
|
||||
/** 单次拉取结果(一页或一批) */
|
||||
export interface ConversationListPage {
|
||||
items: ConversationResponse[];
|
||||
/** 在当前源上是否还能再 loadNext 一页 */
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页式会话列表源:restart 后开始新序列;首次 loadNext 为第一页,之后为后续页。
|
||||
*/
|
||||
export interface IConversationListPagedSource {
|
||||
restart(): void;
|
||||
loadNext(): Promise<ConversationListPage>;
|
||||
/** 至少成功 loadNext 过一次且仍有下一页时为 true */
|
||||
readonly hasMore: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 远端会话列表(offset 与 cursor 共用实现)
|
||||
* 均通过 messageService.fetchRemoteConversationListPage 拉取并落库,行为一致。
|
||||
*/
|
||||
export class NetworkRemoteConversationListPagedSource implements IConversationListPagedSource {
|
||||
private nextPage = 1;
|
||||
private nextCursor: string | null = null;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly pageSize: number;
|
||||
private readonly mode: RemoteConversationListSourceKind;
|
||||
|
||||
constructor(
|
||||
mode: RemoteConversationListSourceKind,
|
||||
pageSize: number = CONVERSATION_LIST_PAGE_SIZE
|
||||
) {
|
||||
this.mode = mode;
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextPage = 1;
|
||||
this.nextCursor = null;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<ConversationListPage> {
|
||||
const result = await messageService.fetchRemoteConversationListPage({
|
||||
mode: this.mode,
|
||||
pageSize: this.pageSize,
|
||||
...(this.mode === 'offset'
|
||||
? { page: this.nextPage }
|
||||
: { cursor: this.nextCursor }),
|
||||
});
|
||||
|
||||
this.hasMoreAfterLastLoad = result.hasMore;
|
||||
if (this.mode === 'offset') {
|
||||
this.nextPage = result.nextPage ?? this.nextPage + 1;
|
||||
} else {
|
||||
this.nextCursor = result.nextCursor ?? null;
|
||||
}
|
||||
this.loadedOnce = true;
|
||||
return { items: result.items, hasMore: result.hasMore };
|
||||
}
|
||||
}
|
||||
|
||||
/** SQLite 会话列表缓存(单批,无后续页) */
|
||||
export class SqliteConversationListPagedSource implements IConversationListPagedSource {
|
||||
private consumed = false;
|
||||
|
||||
restart(): void {
|
||||
this.consumed = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<ConversationListPage> {
|
||||
if (this.consumed) {
|
||||
return { items: [], hasMore: false };
|
||||
}
|
||||
this.consumed = true;
|
||||
try {
|
||||
const items = await conversationRepository.getListCache();
|
||||
return { items, hasMore: false };
|
||||
} catch (e) {
|
||||
console.warn('[SqliteConversationListPagedSource] 读取会话列表缓存失败:', e);
|
||||
return { items: [], hasMore: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 远端会话列表分页策略 */
|
||||
export type RemoteConversationListSourceKind = 'cursor' | 'offset';
|
||||
|
||||
export function createRemoteConversationListSource(
|
||||
kind: RemoteConversationListSourceKind,
|
||||
pageSize: number = CONVERSATION_LIST_PAGE_SIZE
|
||||
): IConversationListPagedSource {
|
||||
return new NetworkRemoteConversationListPagedSource(kind, pageSize);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ConversationResponse, MessageResponse, UserDTO } from '../../types/dto';
|
||||
import type { IConversationListPagedSource } from '../conversationListSources';
|
||||
import type { IConversationListPagedSource } from './sources';
|
||||
|
||||
export interface MessageManagerState {
|
||||
conversations: Map<string, ConversationResponse>;
|
||||
|
||||
Reference in New Issue
Block a user