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:
@@ -1,6 +1,10 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol RDReaderCachePolicyProviding {
|
||||
var shouldAvoidReaderPageCaching: Bool { get }
|
||||
}
|
||||
|
||||
final class RDReaderPreloadController {
|
||||
|
||||
var radius: Int = 2
|
||||
@@ -76,7 +80,11 @@ final class RDReaderPreloadController {
|
||||
) -> UIView {
|
||||
let reusableView = detachedReusablePageView(for: pageNum)
|
||||
let view = contentViewProvider(pageNum, reusableView) ?? reusableView ?? UIView()
|
||||
pageCurlCachedViews[pageNum] = view
|
||||
if shouldCache(view: view, for: pageNum, environment: environment) {
|
||||
pageCurlCachedViews[pageNum] = view
|
||||
} else {
|
||||
pageCurlCachedViews.removeValue(forKey: pageNum)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -105,12 +113,20 @@ final class RDReaderPreloadController {
|
||||
for targetPage in targets {
|
||||
let existing = detachedReusablePageView(for: targetPage)
|
||||
let contentView = contentViewProvider(targetPage, existing) ?? existing ?? UIView()
|
||||
preloadedPageViews[targetPage] = contentView
|
||||
let shouldCacheContentView = shouldCache(view: contentView, for: targetPage, environment: environment)
|
||||
if shouldCacheContentView {
|
||||
preloadedPageViews[targetPage] = contentView
|
||||
} else {
|
||||
preloadedPageViews.removeValue(forKey: targetPage)
|
||||
}
|
||||
if contentView.superview !== preloadHostView {
|
||||
contentView.removeFromSuperview()
|
||||
preloadHostView.addSubview(contentView)
|
||||
}
|
||||
contentView.frame = preloadHostView.bounds
|
||||
if !shouldCacheContentView {
|
||||
contentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +155,14 @@ final class RDReaderPreloadController {
|
||||
&& pageNum < environment.totalPages
|
||||
}
|
||||
|
||||
private func shouldCache(view: UIView, for pageNum: Int, environment: Environment) -> Bool {
|
||||
guard shouldCachePage(pageNum, environment: environment) else { return false }
|
||||
if let policyProvider = view as? RDReaderCachePolicyProviding {
|
||||
return !policyProvider.shouldAvoidReaderPageCaching
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func isDualPage(environment: Environment) -> Bool {
|
||||
environment.landscapeDualPageEnabled
|
||||
&& environment.isLandscape
|
||||
|
||||
@@ -96,6 +96,11 @@ extension RDReaderView: UIPageViewControllerDataSource, UIPageViewControllerDele
|
||||
}
|
||||
|
||||
public func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
|
||||
let resolvedPage = (pageViewController.viewControllers?.first as? RDReaderPageChildViewController)?.pageNum ?? -1
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"PageCurl",
|
||||
"didFinishAnimating finished=\(finished) completed=\(completed) resolvedPage=\(resolvedPage + 1) currentPageBeforeFinish=\(currentPage + 1)"
|
||||
)
|
||||
if completed, let firstVC = pageViewController.viewControllers?.first as? RDReaderPageChildViewController {
|
||||
let pn = firstVC.pageNum
|
||||
if pn != RDReaderView.blankPageNum && pn != RDReaderView.blankEndPageNum {
|
||||
@@ -114,8 +119,17 @@ extension RDReaderView: UIPageViewControllerDataSource, UIPageViewControllerDele
|
||||
if let target = pendingViewControllers.first as? RDReaderPageChildViewController,
|
||||
target.pageNum != RDReaderView.blankPageNum,
|
||||
target.pageNum != RDReaderView.blankEndPageNum {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"PageCurl",
|
||||
"willTransitionTo targetPage=\(target.pageNum + 1) currentPage=\(currentPage + 1)"
|
||||
)
|
||||
predictedPageDirection = target.pageNum >= currentPage
|
||||
primePageCache(around: target.pageNum, preferredForward: predictedPageDirection)
|
||||
} else {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"PageCurl",
|
||||
"willTransitionTo blank target currentPage=\(currentPage + 1)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +267,10 @@ public class RDReaderView: UIView {
|
||||
set { pagingController.pendingTransitionRequest = newValue }
|
||||
}
|
||||
|
||||
var isPageCurlTransitioning: Bool {
|
||||
currentDisplayType == .pageCurl && pagingController.isTransitioning
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
accessibilityIdentifier = "epub.reader.content"
|
||||
@@ -401,6 +405,8 @@ public class RDReaderView: UIView {
|
||||
if let pending = pagingController.finishPageCurlTransition() {
|
||||
transitionToPage(pageNum: pending.pageNum, animated: pending.animated)
|
||||
}
|
||||
|
||||
updatePagingInteractionSuppression()
|
||||
}
|
||||
|
||||
private var preloadEnvironment: RDReaderPreloadController.Environment {
|
||||
@@ -603,12 +609,24 @@ public class RDReaderView: UIView {
|
||||
}
|
||||
|
||||
private func updatePagingInteractionSuppression() {
|
||||
let shouldSuppress = selectionPagingSuppressedContentViews.allObjects.isEmpty == false
|
||||
let shouldSuppress = activePagingSuppressionContentViews().isEmpty == false
|
||||
guard shouldSuppress != isPagingInteractionSuppressed else { return }
|
||||
isPagingInteractionSuppressed = shouldSuppress
|
||||
setPagingInteractionEnabled(!shouldSuppress)
|
||||
}
|
||||
|
||||
private func activePagingSuppressionContentViews() -> [UIView] {
|
||||
selectionPagingSuppressedContentViews.allObjects.filter { contentView in
|
||||
guard contentView.window != nil,
|
||||
contentView.isDescendant(of: self),
|
||||
contentView.bounds.isEmpty == false else {
|
||||
return false
|
||||
}
|
||||
let frameInReader = contentView.convert(contentView.bounds, to: self)
|
||||
return frameInReader.intersects(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
private func setPagingInteractionEnabled(_ isEnabled: Bool) {
|
||||
collectionView.isScrollEnabled = isEnabled
|
||||
pageViewController.gestureRecognizers.forEach { $0.isEnabled = isEnabled }
|
||||
@@ -721,7 +739,13 @@ public class RDReaderView: UIView {
|
||||
}
|
||||
|
||||
public func reloadPageCountOnly() {
|
||||
collectionView.reloadData()
|
||||
if currentDisplayType == .pageCurl {
|
||||
if !isPageCurlTransitioning, currentPage >= 0, numberOfPages() > 0 {
|
||||
transitionToPage(pageNum: currentPage, animated: false)
|
||||
}
|
||||
} else {
|
||||
collectionView.reloadData()
|
||||
}
|
||||
topToolView = resolvedTopChromeView()
|
||||
bottomToolView = resolvedBottomChromeView()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user