import Foundation /// EPUB 阅读器位置协调器:负责阅读位置的恢复、查询和持久化。 /// /// 职责: /// - 根据保存的位置恢复阅读进度 /// - 获取当前可见页面的阅读位置 /// - 从持久化存储加载已保存位置 /// - 持久化当前位置并通知委托 final class RDEPUBReaderLocationCoordinator { private unowned let context: RDEPUBReaderContext /// 上次翻页时的 spineIndex,用于检测跨章翻页 private var lastPageChangeSpineIndex: Int? init(context: RDEPUBReaderContext) { self.context = context } /// 恢复到指定阅读位置,返回是否成功跳转。 @discardableResult func restoreReadingLocation( _ location: RDEPUBLocation, animated: Bool = false, targetHighlightRangeInfo: String? = nil ) -> Bool { guard let controller = context.controller, let readerView = context.readerView else { return false } if context.bookPageMap != nil { _ = context.runtime?.ensureOnDemandNavigationTargetAvailable(for: location) } guard let targetPageNumber = controller.pageNumber(for: location) else { readerView.transitionToPage(pageNum: 0) context.readingSession?.transition(to: .idle) return false } if context.bookPageMap != nil { guard context.runtime?.prepareOnDemandChapter(forAbsolutePageNumber: targetPageNumber) == true else { return false } _ = context.readingSession?.queueNavigation( to: location, relativeToSpineIndex: nil, bookIdentifier: context.currentBookIdentifier, targetHighlightRangeInfo: targetHighlightRangeInfo ) } else if context.textBook == nil { _ = context.readingSession?.queueNavigation( to: location, relativeToSpineIndex: nil, bookIdentifier: context.currentBookIdentifier, targetHighlightRangeInfo: targetHighlightRangeInfo ) } else { context.readingSession?.transition(to: .jumping) } readerView.transitionToPage(pageNum: max(targetPageNumber - 1, 0), animated: animated) // 记录翻页到 JumpSession recordPageChangeIfNeeded() return true } /// 获取当前可见页面对应的阅读位置。 func currentVisibleLocation() -> RDEPUBLocation? { guard let controller = context.controller, let readerView = context.readerView else { return nil } let pageNumber = readerView.currentPage + 1 if (context.textBook != nil || context.bookPageMap != nil), readerView.currentPage >= 0 { if let location = controller.resolvedTextLocation(forPageNumber: pageNumber) { return location } // resolvedTextLocation 可能因章节数据未加载而返回 nil, // 通过 readingSession 的 activePages 构建回退位置 if let readingSession = context.readingSession, readingSession.activePages.indices.contains(readerView.currentPage) { return readingSession.fallbackLocation( for: readingSession.activePages[readerView.currentPage], bookIdentifier: context.currentBookIdentifier ) } } return context.readingSession?.currentReadingLocation(bookIdentifier: context.currentBookIdentifier) } /// 从持久化存储加载上次保存的阅读位置。 func persistenceLocation() -> RDEPUBLocation? { guard let controller = context.controller, let currentBookIdentifier = context.currentBookIdentifier else { return nil } return controller.persistence?.loadLocation(for: currentBookIdentifier) } /// 持久化阅读位置,并通知委托更新目录项和书签状态。 func persist(location: RDEPUBLocation) { guard let controller = context.controller, let currentBookIdentifier = context.currentBookIdentifier else { return } controller.persistence?.saveLocation(location, for: currentBookIdentifier) controller.delegate?.epubReader(controller, didUpdateLocation: location) controller.delegate?.epubReader(controller, didUpdateCurrentTableOfContentsItem: controller.currentTableOfContentsItem) controller.updateReaderChrome() } /// 记录翻页到 JumpSession func recordPageChangeIfNeeded() { guard let runtime = context.runtime, let bookPageMap = context.bookPageMap, let readerView = context.readerView else { return } let currentPageNumber = readerView.currentPage + 1 guard let currentSpineIndex = bookPageMap.spineIndex(forAbsolutePage: currentPageNumber - 1) else { return } if let lastSpineIndex = lastPageChangeSpineIndex, lastSpineIndex != currentSpineIndex { runtime.jumpSessionManager.recordPageChange( fromSpineIndex: lastSpineIndex, toSpineIndex: currentSpineIndex ) } lastPageChangeSpineIndex = currentSpineIndex // 检查是否应该结束 JumpSession let isIdle = context.secondsSinceLastUserNavigation() > 2.0 if let endReason = runtime.jumpSessionManager.checkSessionEnd( currentSpineIndex: currentSpineIndex, isIdle: isIdle ) { runtime.jumpSessionManager.endSession(endReason) } } /// 重置翻页状态(用于重新加载等场景) func resetPageChangeState() { lastPageChangeSpineIndex = nil } }