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,13 +1,7 @@
import UIKit
/// EPUB
///
///
/// - highlightannotation
/// - bookmark
/// -
/// -
final class RDEPUBReaderAnnotationCoordinator {
private unowned let context: RDEPUBReaderContext
init(context: RDEPUBReaderContext) {
@@ -18,19 +12,16 @@ final class RDEPUBReaderAnnotationCoordinator {
context.controller
}
/// ID
func bookmark(withID id: String) -> RDEPUBBookmark? {
guard let controller else { return nil }
return controller.activeBookmarks.first { $0.id == id }
}
/// ID
func highlight(withID id: String) -> RDEPUBHighlight? {
guard let controller else { return nil }
return controller.activeHighlights.first { $0.id == id }
}
///
func updateCurrentSelection(_ selection: RDEPUBSelection?) {
if let selection, !selection.isEmpty {
applySelectionState(.selected(selection))
@@ -39,9 +30,6 @@ final class RDEPUBReaderAnnotationCoordinator {
}
}
///
/// `.selected` context chrome delegate
/// `.idle` chrome delegate
func applySelectionState(_ state: RDEPUBSelectionState) {
guard let controller else { return }
context.selectionState = state
@@ -52,9 +40,6 @@ final class RDEPUBReaderAnnotationCoordinator {
case .selecting:
break
case .selected(let selection):
if controller.readerView.isShowToolView == false {
controller.readerView.tapCenter()
}
controller.updateReaderChrome()
controller.delegate?.epubReader(controller, didChangeSelection: selection)
case .committingAction:
@@ -62,7 +47,6 @@ final class RDEPUBReaderAnnotationCoordinator {
}
}
///
@discardableResult
func addHighlight(
from selection: RDEPUBSelection? = nil,
@@ -72,7 +56,6 @@ final class RDEPUBReaderAnnotationCoordinator {
addAnnotation(from: selection, style: .highlight, color: color, note: note)
}
/// /线/
@discardableResult
func addAnnotation(
from selection: RDEPUBSelection? = nil,
@@ -115,7 +98,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return newHighlight
}
/// upsert ID
@discardableResult
func upsertHighlight(_ highlight: RDEPUBHighlight) -> RDEPUBHighlight? {
guard let controller else { return nil }
@@ -132,7 +114,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return scopedHighlight
}
/// ID
@discardableResult
func removeHighlight(id: String) -> RDEPUBHighlight? {
guard let controller else { return nil }
@@ -144,7 +125,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return removed
}
///
@discardableResult
func updateHighlightNote(id: String, note: String?) -> RDEPUBHighlight? {
guard let controller else { return nil }
@@ -156,7 +136,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return controller.activeHighlights[index]
}
///
@discardableResult
func go(toHighlightID id: String, animated: Bool = true) -> Bool {
guard let highlight = highlight(withID: id) else {
@@ -165,7 +144,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return navigate(to: highlight, animated: animated)
}
///
func removeAllHighlights() {
guard let controller else { return }
guard !controller.activeHighlights.isEmpty else { return }
@@ -173,7 +151,6 @@ final class RDEPUBReaderAnnotationCoordinator {
persistHighlightsAndRefreshContent()
}
/// spine
func scopedSelection(
_ selection: RDEPUBSelection,
relativeToSpineIndex spineIndex: Int?
@@ -190,7 +167,10 @@ final class RDEPUBReaderAnnotationCoordinator {
progression: selection.location.progression,
lastProgression: selection.location.lastProgression,
fragment: selection.location.fragment,
rangeAnchor: selection.location.rangeAnchor
rangeAnchor: selection.location.rangeAnchor,
cfi: selection.location.cfi,
lastCFI: selection.location.lastCFI,
rangeCFI: selection.location.rangeCFI
)
return RDEPUBSelection(
bookIdentifier: controller.currentBookIdentifier,
@@ -201,7 +181,6 @@ final class RDEPUBReaderAnnotationCoordinator {
)
}
///
func presentHighlightsManager() {
guard let controller else { return }
guard controller.configuration.allowsHighlights else { return }
@@ -231,7 +210,6 @@ final class RDEPUBReaderAnnotationCoordinator {
controller.present(navigationController, animated: true)
}
/// /线/
func presentAnnotationCreation() {
guard let controller else { return }
guard controller.configuration.allowsHighlights,
@@ -241,7 +219,6 @@ final class RDEPUBReaderAnnotationCoordinator {
presentAnnotationActionSheet(for: currentSelection)
}
/// /
func presentHighlightActions(for highlight: RDEPUBHighlight, sourceView: UIView, sourceRect: CGRect) {
guard let controller else { return }
let alert = UIAlertController(title: "标注操作", message: highlight.text, preferredStyle: .actionSheet)
@@ -263,7 +240,6 @@ final class RDEPUBReaderAnnotationCoordinator {
controller.present(alert, animated: true)
}
///
func handleSelectionMenuAction(_ action: RDEPUBAnnotationMenuAction, selection: RDEPUBSelection?) {
guard let selection else { return }
switch action {
@@ -277,7 +253,6 @@ final class RDEPUBReaderAnnotationCoordinator {
}
}
///
@discardableResult
func addBookmark(note: String? = nil) -> RDEPUBBookmark? {
guard let controller else { return nil }
@@ -299,7 +274,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return newBookmark
}
///
@discardableResult
func toggleBookmark(note: String? = nil) -> RDEPUBBookmark? {
guard let controller else { return nil }
@@ -315,7 +289,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return addBookmark(note: note)
}
/// ID
@discardableResult
func removeBookmark(id: String) -> RDEPUBBookmark? {
guard let controller else { return nil }
@@ -327,7 +300,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return removed
}
///
@discardableResult
func go(toBookmarkID id: String, animated: Bool = true) -> Bool {
guard let controller else { return false }
@@ -337,7 +309,6 @@ final class RDEPUBReaderAnnotationCoordinator {
return controller.restoreReadingLocation(bookmark.location, animated: animated)
}
///
func presentBookmarksManager() {
guard let controller else { return }
guard !controller.activeBookmarks.isEmpty else { return }
@@ -374,7 +345,10 @@ final class RDEPUBReaderAnnotationCoordinator {
progression: highlight.location.progression,
lastProgression: highlight.location.lastProgression,
fragment: highlight.location.fragment,
rangeAnchor: highlight.location.rangeAnchor
rangeAnchor: highlight.location.rangeAnchor,
cfi: highlight.location.cfi,
lastCFI: highlight.location.lastCFI,
rangeCFI: highlight.location.rangeCFI
)
return RDEPUBHighlight(
id: highlight.id,
@@ -407,7 +381,21 @@ final class RDEPUBReaderAnnotationCoordinator {
}
controller.delegate?.epubReader(controller, didUpdateHighlights: controller.activeHighlights)
controller.updateReaderChrome()
controller.refreshVisibleContentPreservingLocation()
refreshVisibleContentPreservingCurrentPage()
}
private func refreshVisibleContentPreservingCurrentPage() {
guard let controller else { return }
let currentPage = controller.readerView.currentPage
guard currentPage >= 0 else {
controller.refreshVisibleContentPreservingLocation()
return
}
controller.readerView.reloadData()
if controller.readerView.currentPage != currentPage {
controller.readerView.transitionToPage(pageNum: currentPage, animated: false)
}
}
private func presentAnnotationActionSheet(for selection: RDEPUBSelection) {
@@ -511,6 +499,11 @@ final class RDEPUBReaderAnnotationCoordinator {
return false
}
if let bookmarkCFI = bookmark.location.cfi,
let locationCFI = location.cfi {
return bookmarkCFI == locationCFI
}
if let bookmarkAnchor = bookmark.location.rangeAnchor,
let locationAnchor = location.rangeAnchor {
return bookmarkAnchor == locationAnchor
@@ -545,7 +538,10 @@ final class RDEPUBReaderAnnotationCoordinator {
progression: location.progression,
lastProgression: location.lastProgression,
fragment: location.fragment,
rangeAnchor: location.rangeAnchor
rangeAnchor: location.rangeAnchor,
cfi: location.cfi,
lastCFI: location.lastCFI,
rangeCFI: location.rangeCFI
)
}
@@ -559,7 +555,10 @@ final class RDEPUBReaderAnnotationCoordinator {
progression: location.progression,
lastProgression: location.lastProgression,
fragment: location.fragment,
rangeAnchor: location.rangeAnchor
rangeAnchor: location.rangeAnchor,
cfi: location.cfi,
lastCFI: location.lastCFI,
rangeCFI: location.rangeCFI
)
}