import Foundation final class RDEPUBReaderLocationCoordinator { private unowned let context: RDEPUBReaderContext 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) 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 } 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() } 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 let isIdle = context.secondsSinceLastUserNavigation() > 2.0 if let endReason = runtime.jumpSessionManager.checkSessionEnd( currentSpineIndex: currentSpineIndex, isIdle: isIdle ) { runtime.jumpSessionManager.endSession(endReason) } } func resetPageChangeState() { lastPageChangeSpineIndex = nil } }