feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化

- 实现EPUB阅读器搜索功能及选中注释功能
- 优化CFI模块,修复代码审查发现的11个问题
- 实现大书远距目录跳转与后台补全优化方案
- 优化设置面板与章节运行时联动
- 重构及大量改进优化
This commit is contained in:
shenlei
2026-06-22 20:26:34 +08:00
parent f50495ad91
commit c65c190b71
178 changed files with 11380 additions and 6728 deletions
@@ -1,32 +1,9 @@
import UIKit
// MARK: -
/// EPUB
/// EPUBUI
/// EPUB URL TextBook
///
/// **** EPUBUI Core Parser Foundation
///
/// ****
/// - EPUB
/// -
/// -
/// - UI
/// -
///
/// **** init(epubURL:) viewDidLoad RDEPUBParser.parse paginatePublication readerView.reloadData restoreLocation
// MARK: -
public final class RDEPUBReaderController: UIViewController {
// MARK: -
///
public weak var delegate: RDEPUBReaderDelegate?
///
public var configuration: RDEPUBReaderConfiguration {
didSet {
readerContext.configuration = configuration
@@ -54,65 +31,58 @@ public final class RDEPUBReaderController: UIViewController {
}
}
///
public var currentLocation: RDEPUBLocation? {
currentVisibleLocation()
}
/// 1 nil
public var currentPageNumber: Int? {
guard readerView.currentPage >= 0 else { return nil }
return readerView.currentPage + 1
}
/// /
public internal(set) var currentSelection: RDEPUBSelection? {
get { readerContext.currentSelection }
set { readerContext.currentSelection = newValue }
}
///
public var highlights: [RDEPUBHighlight] {
activeHighlights
}
///
public var bookmarks: [RDEPUBBookmark] {
activeBookmarks
}
/// WXRead WRBookmark
public var annotations: [RDEPUBAnnotation] {
let merged = activeHighlights.map(\.annotation) + activeBookmarks.map(\.annotation)
return merged.sorted { $0.createdAt < $1.createdAt }
}
///
public var tableOfContents: [EPUBTableOfContentsItem] {
publication?.tableOfContents ?? []
}
///
public var flattenedTableOfContents: [RDEPUBReaderTableOfContentsItem] {
guard let publication else { return [] }
return flattenedTableOfContentsItems(from: publication.tableOfContents)
}
///
public var currentTableOfContentsItem: RDEPUBReaderTableOfContentsItem? {
resolvedCurrentTableOfContentsItem()
}
// MARK: -
/// EPUB URL
let epubURL: URL
let persistence: RDEPUBReaderPersistence?
let dependencies: RDEPUBReaderDependencies
let readerView = RDReaderView()
let loadingIndicator: UIActivityIndicatorView = {
UIActivityIndicatorView(style: .large)
}()
let errorLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
@@ -121,97 +91,115 @@ public final class RDEPUBReaderController: UIViewController {
label.isHidden = true
return label
}()
let paginationHostView = UIView()
lazy var readerContext = RDEPUBReaderContext(controller: self)
var parser: RDEPUBParser? {
get { readerContext.parser }
set { readerContext.parser = newValue }
}
var publication: RDEPUBPublication? {
get { readerContext.publication }
set { readerContext.publication = newValue }
}
var readingSession: RDEPUBReadingSession? {
get { readerContext.readingSession }
set { readerContext.readingSession = newValue }
}
var textBook: RDEPUBTextBook? {
get { readerContext.textBook }
set { readerContext.textBook = newValue }
}
var activePages: [EPUBPage] {
readerContext.activePages
}
var activeChapters: [EPUBChapterInfo] {
readerContext.activeChapters
}
var activeBookmarks: [RDEPUBBookmark] {
get { readerContext.activeBookmarks }
set { readerContext.activeBookmarks = newValue }
}
var activeHighlights: [RDEPUBHighlight] {
get { readerContext.activeHighlights }
set { readerContext.activeHighlights = newValue }
}
lazy var topToolView = runtime.makeTopToolView()
lazy var bottomToolView = runtime.makeBottomToolView()
lazy var searchBarView = RDEPUBReaderSearchBarView()
///
private(set) var isSearchBarVisible = false
var currentBookIdentifier: String? {
get { readerContext.currentBookIdentifier }
set { readerContext.currentBookIdentifier = newValue }
}
var textBookCache: RDEPUBTextBookCache { readerContext.textBookCache }
var currentBrightness: CGFloat {
get { readerContext.currentBrightness }
set { readerContext.currentBrightness = newValue }
}
var didStartInitialLoad: Bool {
get { readerContext.didStartInitialLoad }
set { readerContext.didStartInitialLoad = newValue }
}
var isRepaginating: Bool {
get { readerContext.isRepaginating }
set { readerContext.isRepaginating = newValue }
}
var lastTextPaginationPageSize: CGSize? {
get { readerContext.lastTextPaginationPageSize }
set { readerContext.lastTextPaginationPageSize = newValue }
}
var isReconcilingTextPaginationSize = false
var paginationToken: UUID {
get { readerContext.paginationToken }
set { readerContext.paginationToken = newValue }
}
var paginator: RDEPUBPaginator? {
get { readerContext.paginator }
set { readerContext.paginator = newValue }
}
var searchState: RDEPUBSearchState? {
get { readerContext.searchState }
set { readerContext.searchState = newValue }
}
var isExternalTextBook: Bool {
get { readerContext.isExternalTextBook }
set { readerContext.isExternalTextBook = newValue }
}
var textFileURL: URL? {
get { readerContext.textFileURL }
set { readerContext.textFileURL = newValue }
}
lazy var readerAssemblyCoordinator = RDEPUBReaderAssemblyCoordinator(context: readerContext)
lazy var runtime = RDEPUBReaderRuntime(context: readerContext)
// MARK: -
/// 使 EPUB URL
///
/// - Parameters:
/// - epubURL: EPUB URL
/// - configuration: 使 .default
/// - persistence: 使 UserDefaults
public init(
epubURL: URL,
configuration: RDEPUBReaderConfiguration = .default,
@@ -225,7 +213,7 @@ public final class RDEPUBReaderController: UIViewController {
self.dependencies = dependencies
let brightness = max(0, min(1, persistedSettings?.brightness ?? dependencies.environment.currentBrightness))
super.init(nibName: nil, bundle: nil)
// Sync context after super.init
readerContext.dependencies = dependencies
readerContext.configuration = self.configuration
readerContext.epubURL = epubURL
@@ -245,15 +233,6 @@ public final class RDEPUBReaderController: UIViewController {
)
}
/// 使 TextBook EPUB
/// TXT TextBook
/// - Parameters:
/// - textBook: TextBook
/// - bookIdentifier:
/// - title:
/// - textFileURL: URL TextBook
/// - configuration:
/// - persistence:
public convenience init(
textBook: RDEPUBTextBook,
bookIdentifier: String,
@@ -317,10 +296,6 @@ public final class RDEPUBReaderController: UIViewController {
runtime.viewportMonitor.viewWillTransition(with: coordinator)
}
// MARK: -
///
/// `handleToolViewVisibilityChanged`
func showSearchBar() {
guard !isSearchBarVisible else { return }
isSearchBarVisible = true
@@ -367,7 +342,6 @@ public final class RDEPUBReaderController: UIViewController {
}
}
/// readerView
private func installSearchBarView() {
guard searchBarView.superview == nil else { return }
searchBarView.apply(theme: configuration.theme)
@@ -403,8 +377,6 @@ public final class RDEPUBReaderController: UIViewController {
}
}
///
/// - Parameter clearSearch:
func hideSearchBar(clearSearch: Bool = false) {
guard isSearchBarVisible else { return }
isSearchBarVisible = false
@@ -425,7 +397,6 @@ public final class RDEPUBReaderController: UIViewController {
}
}
///
func updateSearchCount() {
guard let searchState else {
searchBarView.showNoResults()
@@ -438,7 +409,6 @@ public final class RDEPUBReaderController: UIViewController {
)
}
/// RDReaderView
func handleToolViewVisibilityChanged(isVisible: Bool) {
if isVisible {
if isSearchBarVisible {
@@ -456,6 +426,7 @@ public final class RDEPUBReaderController: UIViewController {
}
private extension RDEPUBReaderController {
func searchResultSections(for searchState: RDEPUBSearchState) -> [RDEPUBReaderSearchSection] {
let groupedMatches = Dictionary(grouping: Array(searchState.matches.enumerated()), by: { entry in
searchSectionTitle(for: entry.element)