refactor(MessageManager): enhance conversation list handling and integrate local source fallback
- Update MessageManager to support both remote and local conversation list sources, improving data retrieval and offline capabilities. - Refactor hooks and components to utilize the new MessageManager structure, ensuring seamless integration of cursor pagination and local caching. - Introduce new types and methods for better management of conversation data and loading states. - Modify MessageListScreen to leverage the updated hooks for fetching conversations, enhancing user experience with improved loading and pagination handling.
This commit is contained in:
133
src/stores/conversationListSources.ts
Normal file
133
src/stores/conversationListSources.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 会话列表数据源抽象:游标、常规页码、SQLite 等实现同一契约,MessageManager 只依赖接口。
|
||||
*/
|
||||
|
||||
import { ConversationResponse } from '../types/dto';
|
||||
import { messageService } from '../services/messageService';
|
||||
import { getConversationListCache } from '../services/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;
|
||||
}
|
||||
|
||||
/** 远端常规分页:GET /conversations?page=&page_size=(始终请求网络,避免走 getConversations 的本地短路) */
|
||||
export class NetworkOffsetConversationListPagedSource implements IConversationListPagedSource {
|
||||
private nextPage = 1;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly pageSize: number;
|
||||
|
||||
constructor(pageSize: number = CONVERSATION_LIST_PAGE_SIZE) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextPage = 1;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<ConversationListPage> {
|
||||
const response = await messageService.getConversations(this.nextPage, this.pageSize, true);
|
||||
const items = response.list || [];
|
||||
const totalPages = Math.max(0, response.total_pages ?? 0);
|
||||
const currentPage = response.page ?? this.nextPage;
|
||||
this.hasMoreAfterLastLoad = totalPages > 0 && currentPage < totalPages;
|
||||
this.nextPage = currentPage + 1;
|
||||
this.loadedOnce = true;
|
||||
return { items, hasMore: this.hasMoreAfterLastLoad };
|
||||
}
|
||||
}
|
||||
|
||||
/** 远端游标接口 */
|
||||
export class NetworkCursorConversationListPagedSource implements IConversationListPagedSource {
|
||||
private nextCursor: string | null = null;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly pageSize: number;
|
||||
|
||||
constructor(pageSize: number = CONVERSATION_LIST_PAGE_SIZE) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextCursor = null;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<ConversationListPage> {
|
||||
const response = await messageService.getConversationsCursor(
|
||||
this.nextCursor == null
|
||||
? { page_size: this.pageSize }
|
||||
: { cursor: this.nextCursor, page_size: this.pageSize }
|
||||
);
|
||||
const items = response.list || [];
|
||||
this.nextCursor = response.next_cursor ?? null;
|
||||
this.hasMoreAfterLastLoad = Boolean(response.has_more && response.next_cursor != null);
|
||||
this.loadedOnce = true;
|
||||
return { items, hasMore: this.hasMoreAfterLastLoad };
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 getConversationListCache();
|
||||
return { items, hasMore: false };
|
||||
} catch (e) {
|
||||
console.warn('[SqliteConversationListPagedSource] 读取会话列表缓存失败:', e);
|
||||
return { items: [], hasMore: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 未注入 remote 时,按类型创建默认远端源 */
|
||||
export type RemoteConversationListSourceKind = 'cursor' | 'offset';
|
||||
|
||||
export function createRemoteConversationListSource(
|
||||
kind: RemoteConversationListSourceKind,
|
||||
pageSize: number = CONVERSATION_LIST_PAGE_SIZE
|
||||
): IConversationListPagedSource {
|
||||
return kind === 'offset'
|
||||
? new NetworkOffsetConversationListPagedSource(pageSize)
|
||||
: new NetworkCursorConversationListPagedSource(pageSize);
|
||||
}
|
||||
Reference in New Issue
Block a user