feat: 架构整改 — Context拆分、Runtime拆分、异步章节加载、UI测试覆盖
Phase 1: Context 拆分 - 新增 RDEPUBReaderState/RDEPUBReaderEnvironment/RDEPUBReaderServices - RDEPUBReaderContext 改为过渡门面,代理到 State/Environment/Services Phase 2: Runtime 拆分 - 新增 RDEPUBPresentationRuntime 处理分页状态管理 - 新增 RDEPUBChapterWarmupOrchestrator 处理章节预热与加载编排 - RDEPUBReaderRuntime 从 1277 行收缩,公共 API 转发到新 facade Phase 0.5: 性能优化 - prepareOnDemandChapter 支持异步模式(allowSynchronousLoad: false) - extendPartialBookPageMapIfNeeded 改为 DispatchGroup 并发加载 - RDEPUBChapterOffsetMap.cfiMap 加 NSLock 保护数据竞争 - CFI Map 构建延迟到后台队列(scheduleDeferredCFIMapBuildIfNeeded) - RDEPUBTextPageRenderView 引入静态位图缓存 - RDEPUBTextContentView 新增 loadingSpinner 占位页 Phase 3: 状态机 - 新增 RDEPUBNavigationStateMachine(含 DEBUG 合法转换校验) - 新增 RDEPUBPaginationState 记录分页来源 Review 修复 - makeSummary 重复方法合并 - ensureNavigationTargetAvailable 同步路径加注释标记 UI 测试 - 新增 AsyncChapterLoadingTests(20 个测试,覆盖全部架构整改场景) - 跨章节翻页、延迟 CFI、状态机、内存警告、预加载、位置恢复等
This commit is contained in:
@@ -107,6 +107,13 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate {
|
||||
return label
|
||||
}()
|
||||
|
||||
private let loadingSpinner: UIActivityIndicatorView = {
|
||||
let spinner = UIActivityIndicatorView(style: .medium)
|
||||
spinner.hidesWhenStopped = true
|
||||
spinner.accessibilityIdentifier = "epub.reader.loadingSpinner"
|
||||
return spinner
|
||||
}()
|
||||
|
||||
private lazy var longPressGestureRecognizer: UILongPressGestureRecognizer = {
|
||||
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
|
||||
gesture.minimumPressDuration = 0.5
|
||||
@@ -136,6 +143,7 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate {
|
||||
#endif
|
||||
addSubview(overlayView)
|
||||
addSubview(pageNumberLabel)
|
||||
addSubview(loadingSpinner)
|
||||
addSubview(selectionLoupeView)
|
||||
addGestureRecognizer(longPressGestureRecognizer)
|
||||
addGestureRecognizer(panGestureRecognizer)
|
||||
@@ -214,6 +222,7 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate {
|
||||
width: labelSize.width,
|
||||
height: labelSize.height
|
||||
)
|
||||
loadingSpinner.center = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
}
|
||||
|
||||
func configure(
|
||||
@@ -226,6 +235,7 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate {
|
||||
highlights: [RDEPUBHighlight] = [],
|
||||
searchState: RDEPUBSearchState? = nil
|
||||
) {
|
||||
loadingSpinner.stopAnimating()
|
||||
currentPage = page
|
||||
currentChapterCFIMap = chapterCFIMap
|
||||
currentChapterFragmentOffsets = chapterFragmentOffsets
|
||||
@@ -297,6 +307,44 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate {
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
func configureLoading(
|
||||
pageNumber: Int,
|
||||
totalPages: Int,
|
||||
configuration: RDEPUBReaderConfiguration
|
||||
) {
|
||||
currentPage = nil
|
||||
currentChapterCFIMap = nil
|
||||
currentChapterFragmentOffsets = [:]
|
||||
currentSelection = nil
|
||||
menuSelection = nil
|
||||
activeSelectionHandle = nil
|
||||
updateSelectionInteractionState(.idle)
|
||||
currentHighlights = []
|
||||
currentSearchState = nil
|
||||
selectionController.clearSelection(renderView: coreTextRenderView)
|
||||
contentInsets = configuration.reflowableContentInsets
|
||||
backgroundColor = configuration.theme.contentBackgroundColor
|
||||
pageNumberLabel.textColor = configuration.theme.contentTextColor
|
||||
pageNumberLabel.text = "\(pageNumber) / \(totalPages)"
|
||||
coverImageView.isHidden = true
|
||||
coverImageView.image = nil
|
||||
|
||||
#if canImport(DTCoreText)
|
||||
coreTextContentView.isHidden = false
|
||||
coreTextContentView.backgroundColor = .clear
|
||||
coreTextContentView.attributedDisplayContent = nil
|
||||
coreTextContentView.layoutFrame = nil
|
||||
coreTextDisplayContent = nil
|
||||
coreTextDisplayRange = nil
|
||||
#endif
|
||||
|
||||
overlayView.clearSelection()
|
||||
backgroundOverlayView.clearSelection()
|
||||
delegate?.textContentView(self, didChangeSelection: nil)
|
||||
loadingSpinner.startAnimating()
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
func clearSelection() {
|
||||
currentSelection = nil
|
||||
menuSelection = nil
|
||||
|
||||
Reference in New Issue
Block a user