feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化
- 实现EPUB阅读器搜索功能及选中注释功能 - 优化CFI模块,修复代码审查发现的11个问题 - 实现大书远距目录跳转与后台补全优化方案 - 优化设置面板与章节运行时联动 - 重构及大量改进优化
This commit is contained in:
@@ -1,56 +1,47 @@
|
||||
import Foundation
|
||||
|
||||
/// 远距跳转会话:保护前台阅读窗口不被后台页图覆盖。
|
||||
///
|
||||
/// 当用户执行远距跳转(目录、书签、搜索)后创建,在保护期内:
|
||||
/// - 不允许任何不覆盖保护区的后台页图接管前台
|
||||
/// - 后台补全围绕当前阅读区段优先
|
||||
struct RDEPUBJumpSession {
|
||||
/// 跳转目标的 spineIndex
|
||||
|
||||
let anchorSpineIndex: Int
|
||||
/// 创建时间
|
||||
|
||||
let createdAt: CFAbsoluteTime
|
||||
/// 受保护的 spineIndex 集合
|
||||
|
||||
let protectedSpineIndices: Set<Int>
|
||||
/// 序列号,用于区分多次跳转
|
||||
|
||||
let sequenceNumber: Int
|
||||
/// 过期时间
|
||||
|
||||
let expiresAt: CFAbsoluteTime
|
||||
/// 跳转原因
|
||||
|
||||
let reason: Reason
|
||||
|
||||
/// 跳转原因枚举
|
||||
enum Reason {
|
||||
case tableOfContentsJump
|
||||
case bookmarkJump
|
||||
case searchJump
|
||||
}
|
||||
|
||||
/// 结束条件枚举
|
||||
enum EndReason {
|
||||
/// 候选页图已完整覆盖保护区
|
||||
|
||||
case coverageComplete
|
||||
/// 用户连续翻页离开保护区
|
||||
|
||||
case navigatedAway
|
||||
/// 超时
|
||||
|
||||
case timeout
|
||||
/// 被新的跳转取代
|
||||
|
||||
case superseded
|
||||
}
|
||||
}
|
||||
|
||||
/// JumpSession 策略配置
|
||||
public struct RDEPUBJumpSessionPolicy: Equatable {
|
||||
/// 连续翻页离开保护区的阈值
|
||||
|
||||
public let exitPageThreshold: Int
|
||||
/// 跳转超时时间
|
||||
|
||||
public let timeout: TimeInterval
|
||||
/// 空闲宽限期
|
||||
|
||||
public let idleGracePeriod: TimeInterval
|
||||
/// 保护区相邻章节半径
|
||||
|
||||
public let protectedNeighborRadius: Int
|
||||
|
||||
/// 默认策略
|
||||
public static let `default` = RDEPUBJumpSessionPolicy(
|
||||
exitPageThreshold: 6,
|
||||
timeout: 20,
|
||||
@@ -71,26 +62,20 @@ public struct RDEPUBJumpSessionPolicy: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
/// JumpSession 管理器
|
||||
final class RDEPUBJumpSessionManager {
|
||||
|
||||
private unowned let context: RDEPUBReaderContext
|
||||
|
||||
/// 当前活跃的 JumpSession
|
||||
private(set) var activeSession: RDEPUBJumpSession?
|
||||
|
||||
/// 全局序列号
|
||||
private var nextSequenceNumber: Int = 0
|
||||
|
||||
/// 连续翻页计数器
|
||||
private var consecutivePageCount: Int = 0
|
||||
|
||||
/// 上次翻页方向
|
||||
private var lastPageDirection: PageDirection?
|
||||
|
||||
/// 上次用户活动时间
|
||||
private var lastActivityTime: CFAbsoluteTime = 0
|
||||
|
||||
/// 页面方向
|
||||
enum PageDirection {
|
||||
case forward
|
||||
case backward
|
||||
@@ -100,7 +85,6 @@ final class RDEPUBJumpSessionManager {
|
||||
self.context = context
|
||||
}
|
||||
|
||||
/// 创建新的 JumpSession
|
||||
@discardableResult
|
||||
func createSession(
|
||||
anchorSpineIndex: Int,
|
||||
@@ -110,7 +94,6 @@ final class RDEPUBJumpSessionManager {
|
||||
let policy = context.configuration.jumpSessionPolicy
|
||||
let now = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
// 计算保护区
|
||||
var protectedIndices: Set<Int> = [anchorSpineIndex]
|
||||
for offset in 1...policy.protectedNeighborRadius {
|
||||
let lower = anchorSpineIndex - offset
|
||||
@@ -146,7 +129,6 @@ final class RDEPUBJumpSessionManager {
|
||||
return session
|
||||
}
|
||||
|
||||
/// 记录用户翻页
|
||||
func recordPageChange(fromSpineIndex: Int, toSpineIndex: Int) {
|
||||
guard activeSession != nil else { return }
|
||||
|
||||
@@ -161,29 +143,24 @@ final class RDEPUBJumpSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查是否允许页图接管
|
||||
func shouldAllowPageMapTakeover(candidateSpineIndices: Set<Int>) -> Bool {
|
||||
guard let session = activeSession else {
|
||||
return true // 没有活跃 Session,允许接管
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查候选页图是否覆盖保护区
|
||||
let protectedIndices = session.protectedSpineIndices
|
||||
let coverageRatio = Double(protectedIndices.intersection(candidateSpineIndices).count) /
|
||||
Double(protectedIndices.count)
|
||||
|
||||
// 必须覆盖至少 80% 的保护区
|
||||
return coverageRatio >= 0.8
|
||||
}
|
||||
|
||||
/// 检查是否应该结束 Session
|
||||
func checkSessionEnd(currentSpineIndex: Int, isIdle: Bool) -> RDEPUBJumpSession.EndReason? {
|
||||
guard let session = activeSession else { return nil }
|
||||
|
||||
let now = CFAbsoluteTimeGetCurrent()
|
||||
let policy = context.configuration.jumpSessionPolicy
|
||||
|
||||
// 1. 检查超时
|
||||
if now >= session.expiresAt {
|
||||
if isIdle || (now - lastActivityTime) >= policy.idleGracePeriod {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
@@ -194,7 +171,6 @@ final class RDEPUBJumpSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查是否离开保护区
|
||||
if !session.protectedSpineIndices.contains(currentSpineIndex) {
|
||||
if consecutivePageCount >= policy.exitPageThreshold {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
@@ -204,14 +180,13 @@ final class RDEPUBJumpSessionManager {
|
||||
return .navigatedAway
|
||||
}
|
||||
} else {
|
||||
// 在保护区内,重置连续翻页计数
|
||||
|
||||
consecutivePageCount = 0
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// 结束当前 Session
|
||||
func endSession(_ reason: RDEPUBJumpSession.EndReason) {
|
||||
guard let session = activeSession else { return }
|
||||
RDEPUBBackgroundTrace.log(
|
||||
@@ -223,7 +198,6 @@ final class RDEPUBJumpSessionManager {
|
||||
lastPageDirection = nil
|
||||
}
|
||||
|
||||
/// 清除 Session(用于重新加载等场景)
|
||||
func clearSession() {
|
||||
activeSession = nil
|
||||
consecutivePageCount = 0
|
||||
|
||||
Reference in New Issue
Block a user