feat: 交互协调器拆分、附件提示、暗色图片适配、选区放大镜及文档清理

- 拆分 ContentDelegates/TextContentView 为独立协调器(InteractionCoordinator、LocationResolution、ExternalLinks、AttachmentTooltip)
- 新增 RDEPUBAttachmentTooltipView/OverlayView 附件气泡提示
- 新增 RDEPUBDarkImageAdjuster 暗色模式图片亮度适配
- 新增 RDEPUBSelectionLoupeView 选区放大镜
- 新增 MetadataParseWorker/CancellationController 元数据解析取消机制
- 重构 PresentationRuntime/PaginationCoordinator 精简职责
- 优化 ChapterLoader/WarmupOrchestrator 异步章节加载
- CFI 模块微调与 NoteModels 更新
- 清理冗余文档,更新架构/UML/业务逻辑文档

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-06-24 17:47:24 +08:00
co-authored by Claude
parent 7de661eb54
commit d15f20b097
59 changed files with 4522 additions and 7220 deletions
@@ -33,7 +33,22 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderPageProvider, RDRe
forAbsolutePageNumber: pageNum + 1,
allowSynchronousLoad: false
)
if let resolvedPage = runtime.pageResolver.resolvePage(absolutePageIndex: pageNum) {
var resolvedPage = runtime.pageResolver.resolvePage(absolutePageIndex: pageNum)
let shouldAllowSynchronousFallback = (
readerView.currentPage < 0 || readerView.currentPage == pageNum
) && !readerView.isPageCurlTransitioning
if resolvedPage == nil, shouldAllowSynchronousFallback {
RDEPUBBackgroundTrace.log(
"LoadingPage",
"sync fallback requested page=\(pageNum + 1) currentPage=\(readerView.currentPage + 1) totalKnown=\(pageCountOfReaderView(readerView: readerView))"
)
_ = runtime.prepareOnDemandChapter(
forAbsolutePageNumber: pageNum + 1,
allowSynchronousLoad: true
)
resolvedPage = runtime.pageResolver.resolvePage(absolutePageIndex: pageNum)
}
if let resolvedPage {
let contentView = (containerView as? RDEPUBTextContentView) ?? RDEPUBTextContentView()
contentView.delegate = self
readerView.registerSelectionGestureDependenciesIfNeeded(for: contentView)
@@ -69,6 +84,10 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderPageProvider, RDRe
guard let readerView, let contentView else { return }
readerView.updateSelectionPagingSuppression(for: contentView, isSuppressed: isSuppressed)
}
RDEPUBBackgroundTrace.log(
"LoadingPage",
"configureLoading page=\(pageNum + 1) currentPage=\(readerView.currentPage + 1) totalKnown=\(pageCountOfReaderView(readerView: readerView)) hasMap=\(readerContext.bookPageMap != nil)"
)
contentView.configureLoading(
pageNumber: pageNum + 1,
totalPages: pageCountOfReaderView(readerView: readerView),
@@ -341,9 +360,24 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderPageProvider, RDRe
guard !isRepaginating else { return }
let previousCurrentPage = readerView.currentPage
let wasPageCurlTransitioning = readerView.isPageCurlTransitioning
runtime.applyPendingFullPageMapIfNeeded()
if wasPageCurlTransitioning {
DispatchQueue.main.async { [weak self, weak readerView] in
guard let self, let readerView, !readerView.isPageCurlTransitioning else { return }
self.runtime.applyPendingFullPageMapIfNeeded()
}
}
let effectivePageNum = readerView.currentPage >= 0 ? readerView.currentPage : pageNum
RDEPUBBackgroundTrace.log(
"PageEvent",
"pageNum callback raw=\(pageNum + 1) effective=\(effectivePageNum + 1) previousCurrent=\(previousCurrentPage + 1) readerCurrent=\(readerView.currentPage + 1) totalPages=\(pageCountOfReaderView(readerView: readerView)) display=\(readerView.currentDisplayType)"
)
if previousCurrentPage != readerView.currentPage, effectivePageNum != pageNum {
RDEPUBBackgroundTrace.log(
"PageEvent",
"pageNum callback aborted dueToCurrentPageMutation raw=\(pageNum + 1) effective=\(effectivePageNum + 1)"
)
return
}