refactor: split reader architecture and chrome handling
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import UIKit
|
||||
|
||||
/// 阅读器共享状态中心:所有 coordinator 通过 context 访问业务状态和便捷方法。
|
||||
///
|
||||
/// context 持有:
|
||||
/// - 业务状态(parser、publication、textBook、pages 等)
|
||||
/// - UI 配置(configuration、brightness)
|
||||
/// - 持久化策略(persistence)
|
||||
/// - 便捷方法(renderStyle、layoutConfig 等)
|
||||
/// - 弱引用 controller(仅用于 UIKit 呈现操作)
|
||||
final class RDEPUBReaderContext {
|
||||
// MARK: - 引用
|
||||
|
||||
weak var controller: RDEPUBReaderController?
|
||||
weak var readerView: RDReaderView?
|
||||
var dependencies: RDEPUBReaderDependencies = .live
|
||||
var runtime: RDEPUBReaderRuntime? {
|
||||
controller?.runtime
|
||||
}
|
||||
|
||||
// MARK: - 业务状态
|
||||
|
||||
var parser: RDEPUBParser?
|
||||
var publication: RDEPUBPublication?
|
||||
var readingSession: RDEPUBReadingSession?
|
||||
var textBook: RDEPUBTextBook?
|
||||
var activeBookmarks: [RDEPUBBookmark] = []
|
||||
var activeHighlights: [RDEPUBHighlight] = []
|
||||
var currentBookIdentifier: String?
|
||||
var paginationToken = UUID()
|
||||
var paginator: RDEPUBPaginator?
|
||||
var searchState: RDEPUBSearchState?
|
||||
var lastTextPaginationPageSize: CGSize?
|
||||
var currentSelection: RDEPUBSelection?
|
||||
|
||||
// MARK: - 控制器状态(从 controller 下沉)
|
||||
|
||||
var configuration: RDEPUBReaderConfiguration = .default
|
||||
var persistence: RDEPUBReaderPersistence?
|
||||
var epubURL: URL = URL(string: "about:blank")!
|
||||
var isRepaginating: Bool = false
|
||||
var didStartInitialLoad: Bool = false
|
||||
var isExternalTextBook: Bool = false
|
||||
var textFileURL: URL?
|
||||
var textBookCache = RDEPUBTextBookCache()
|
||||
|
||||
// MARK: - 初始化
|
||||
|
||||
init(controller: RDEPUBReaderController) {
|
||||
self.controller = controller
|
||||
self.readerView = controller.readerView
|
||||
}
|
||||
|
||||
// MARK: - 便捷方法
|
||||
|
||||
func currentLayoutContext() -> RDEPUBNavigatorLayoutContext {
|
||||
let containerSize = readerView?.bounds.size ?? .zero
|
||||
let viewSize = controller?.view.bounds.size ?? containerSize
|
||||
let resolvedSize = containerSize == .zero ? viewSize : containerSize
|
||||
return RDEPUBNavigatorLayoutContext(
|
||||
containerSize: resolvedSize,
|
||||
pagesPerScreen: readerView?.pagesPerScreen ?? 1,
|
||||
safeAreaInsets: controller?.view.safeAreaInsets ?? .zero,
|
||||
userInterfaceIdiom: controller?.traitCollection.userInterfaceIdiom ?? .phone,
|
||||
reflowableContentInsets: configuration.reflowableContentInsets
|
||||
)
|
||||
}
|
||||
|
||||
func currentPreferences() -> RDEPUBPreferences {
|
||||
configuration.makePreferences()
|
||||
}
|
||||
|
||||
func currentTextPageSize() -> CGSize {
|
||||
let pageNum = (readerView?.currentPage ?? -1) >= 0 ? readerView?.currentPage : nil
|
||||
if let readerView, let pageNum {
|
||||
let resolvedSize = readerView.resolvedSinglePageSize(pageNum: pageNum)
|
||||
if resolvedSize.width > 0, resolvedSize.height > 0 {
|
||||
return resolvedSize
|
||||
}
|
||||
}
|
||||
return currentLayoutContext().viewportSize
|
||||
}
|
||||
|
||||
func currentTextRenderStyle() -> RDEPUBTextRenderStyle {
|
||||
let font = UIFont.systemFont(ofSize: configuration.fontSize)
|
||||
let lineSpacing = max(font.lineHeight * (configuration.lineHeightMultiple - 1), 4)
|
||||
return RDEPUBTextRenderStyle(
|
||||
font: font,
|
||||
lineSpacing: lineSpacing,
|
||||
textColor: configuration.theme.contentTextColor,
|
||||
backgroundColor: configuration.theme.contentBackgroundColor
|
||||
)
|
||||
}
|
||||
|
||||
func currentTextLayoutConfig(pageSize: CGSize) -> RDEPUBTextLayoutConfig {
|
||||
return RDEPUBTextLayoutConfig(
|
||||
frameWidth: max(pageSize.width, 1),
|
||||
frameHeight: max(pageSize.height, 1),
|
||||
edgeInsets: configuration.reflowableContentInsets,
|
||||
numberOfColumns: configuration.numberOfColumns,
|
||||
columnGap: configuration.columnGap,
|
||||
avoidOrphans: true,
|
||||
avoidWidows: true,
|
||||
avoidPageBreakInsideEnabled: true,
|
||||
hyphenation: true,
|
||||
imageMaxHeightRatio: 0.85,
|
||||
fallbackViewportSize: dependencies.environment.fallbackViewportSize
|
||||
)
|
||||
}
|
||||
|
||||
func resolvedTextRenderer() -> RDEPUBTextRenderer {
|
||||
dependencies.makeTextRenderer(configuration.textRenderingEngine)
|
||||
}
|
||||
|
||||
var activePages: [EPUBPage] {
|
||||
readingSession?.activePages ?? []
|
||||
}
|
||||
|
||||
var activeChapters: [EPUBChapterInfo] {
|
||||
readingSession?.activeChapters ?? []
|
||||
}
|
||||
|
||||
var currentBrightness: CGFloat {
|
||||
get { dependencies.environment.currentBrightness }
|
||||
set { dependencies.environment.currentBrightness = newValue }
|
||||
}
|
||||
|
||||
func replaceActiveSnapshot(_ snapshot: RDEPUBReadingSession.PaginationSnapshot) {
|
||||
readingSession?.setActiveSnapshot(snapshot)
|
||||
}
|
||||
|
||||
func clearActiveSnapshot() {
|
||||
readingSession?.resetRuntimeState()
|
||||
}
|
||||
|
||||
func makeParser() -> RDEPUBParser {
|
||||
dependencies.makeParser()
|
||||
}
|
||||
|
||||
func makePaginator() -> RDEPUBPaginator {
|
||||
dependencies.makePaginator()
|
||||
}
|
||||
|
||||
func makeTextBookBuilder(layoutConfig: RDEPUBTextLayoutConfig) -> RDEPUBTextBookBuilder {
|
||||
dependencies.makeTextBookBuilder(resolvedTextRenderer(), textBookCache, layoutConfig)
|
||||
}
|
||||
|
||||
func makePlainTextBookBuilder(layoutConfig: RDEPUBTextLayoutConfig) -> RDPlainTextBookBuilder {
|
||||
dependencies.makePlainTextBookBuilder(resolvedTextRenderer(), layoutConfig)
|
||||
}
|
||||
|
||||
func currentVisibleLocation() -> RDEPUBLocation? {
|
||||
controller?.currentVisibleLocation()
|
||||
}
|
||||
|
||||
func persistenceLocation() -> RDEPUBLocation? {
|
||||
guard let currentBookIdentifier else { return nil }
|
||||
return persistence?.loadLocation(for: currentBookIdentifier)
|
||||
}
|
||||
|
||||
func persist(location: RDEPUBLocation) {
|
||||
guard let currentBookIdentifier else { return }
|
||||
persistence?.saveLocation(location, for: currentBookIdentifier)
|
||||
}
|
||||
|
||||
func textChapterData(forNormalizedHref href: String) -> RDEPUBChapterData? {
|
||||
guard let textBook, let publication else { return nil }
|
||||
let normalizedHref = publication.resourceResolver.normalizedHref(href) ?? href
|
||||
return textBook.chapters.lazy
|
||||
.first(where: { (publication.resourceResolver.normalizedHref($0.href) ?? $0.href) == normalizedHref })
|
||||
.flatMap { textBook.chapterData(for: $0.href) }
|
||||
}
|
||||
|
||||
func showLoading() {
|
||||
controller?.showLoading()
|
||||
}
|
||||
|
||||
func hideLoading() {
|
||||
controller?.hideLoading()
|
||||
}
|
||||
|
||||
func handle(error: Error) {
|
||||
controller?.handle(error: error)
|
||||
}
|
||||
|
||||
func updateReaderChrome() {
|
||||
controller?.updateReaderChrome()
|
||||
}
|
||||
|
||||
func refreshVisibleContentPreservingLocation() {
|
||||
controller?.refreshVisibleContentPreservingLocation()
|
||||
}
|
||||
|
||||
func restoreReadingLocation(_ location: RDEPUBLocation, animated: Bool = false) -> Bool {
|
||||
controller?.restoreReadingLocation(location, animated: animated) ?? false
|
||||
}
|
||||
|
||||
func repaginatePreservingCurrentLocation() {
|
||||
controller?.repaginatePreservingCurrentLocation()
|
||||
}
|
||||
|
||||
func applyReaderViewConfiguration() {
|
||||
controller?.applyReaderViewConfiguration()
|
||||
}
|
||||
|
||||
func updateBookmarkChrome() {
|
||||
controller?.updateBookmarkChrome()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user