修复阅读器资源、朗读与持久化稳定性

阅读器在多会话朗读、加密 EPUB 资源、并发打开同一本书和多 UserDefaults 容器等场景下,存在旧异步结果覆盖新状态、锁屏控制串会话、大型明文资源被误拒绝、解压半成品被复用及标注跨容器混用的风险;同时高亮、书签、搜索和设置面板的空状态与自动化可访问性入口不完整。\n\n本次为朗读会话引入代次和当前 utterance 校验,远程控制改为仅响应当前会话并按自身 token 清理;加密资源 provider 先判定是否实际返回解密数据,明文大资源继续流式读取;解压缓存串行化以避免并发复用未完成目录。书签与高亮迁移至受保护文件,按 UserDefaults 容器隔离命名空间,保留标准容器旧文件兼容并确保新副本成功落盘后才删除历史数据。\n\n同步调整缓存淘汰、FoundationModels 弱链接、搜索/标注/设置面板交互与 UI 测试,并更新 Pod 生成配置及 API、风险文档。已执行 git diff --check 和 ReadViewDemo Debug Simulator 构建,结果为 BUILD SUCCEEDED。
This commit is contained in:
shenlei
2026-07-30 20:52:21 +09:00
parent e01cbc169d
commit 3e60bf1869
40 changed files with 3210 additions and 2691 deletions
@@ -16,6 +16,10 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
private let filterControl = UISegmentedControl(items: ["全部", "注释", "划线"])
/// tableView `backgroundView`
/// backgroundView VoiceOver UI
private let emptyStateLabel = UILabel()
private var filteredHighlights: [RDEPUBHighlight] {
switch filterControl.selectedSegmentIndex {
case 1:
@@ -120,19 +124,28 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.highlights.navbar"
}
private func updateEmptyState() {
guard filteredHighlights.isEmpty else {
tableView.backgroundView = nil
return
}
private func installEmptyStateLabelIfNeeded() {
guard emptyStateLabel.superview == nil else { return }
emptyStateLabel.textAlignment = .center
emptyStateLabel.numberOfLines = 0
emptyStateLabel.translatesAutoresizingMaskIntoConstraints = false
emptyStateLabel.accessibilityIdentifier = "epub.reader.highlights.empty"
emptyStateLabel.isAccessibilityElement = true
tableView.addSubview(emptyStateLabel)
//
NSLayoutConstraint.activate([
emptyStateLabel.centerXAnchor.constraint(equalTo: tableView.frameLayoutGuide.centerXAnchor),
emptyStateLabel.centerYAnchor.constraint(equalTo: tableView.frameLayoutGuide.centerYAnchor),
emptyStateLabel.leadingAnchor.constraint(greaterThanOrEqualTo: tableView.frameLayoutGuide.leadingAnchor, constant: 24),
emptyStateLabel.trailingAnchor.constraint(lessThanOrEqualTo: tableView.frameLayoutGuide.trailingAnchor, constant: -24)
])
}
let label = UILabel()
label.text = emptyStateText()
label.textAlignment = .center
label.textColor = theme.contentTextColor.withAlphaComponent(0.7)
label.numberOfLines = 0
label.accessibilityIdentifier = "epub.reader.highlights.empty"
tableView.backgroundView = label
private func updateEmptyState() {
installEmptyStateLabelIfNeeded()
emptyStateLabel.textColor = theme.contentTextColor.withAlphaComponent(0.7)
emptyStateLabel.text = emptyStateText()
emptyStateLabel.isHidden = !filteredHighlights.isEmpty
}
@objc private func filterChangedAction() {
@@ -209,6 +222,9 @@ final class RDEPUBReaderBookmarksViewController: UITableViewController {
return formatter
}()
/// highlights `tableView.backgroundView`
private let emptyStateLabel = UILabel()
init(bookmarks: [RDEPUBBookmark], theme: RDEPUBReaderTheme) {
self.bookmarks = bookmarks.sorted { $0.createdAt > $1.createdAt }
self.theme = theme
@@ -279,18 +295,27 @@ final class RDEPUBReaderBookmarksViewController: UITableViewController {
navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.bookmarks.navbar"
}
private func installEmptyStateLabelIfNeeded() {
guard emptyStateLabel.superview == nil else { return }
emptyStateLabel.text = "暂无书签"
emptyStateLabel.textAlignment = .center
emptyStateLabel.numberOfLines = 0
emptyStateLabel.translatesAutoresizingMaskIntoConstraints = false
emptyStateLabel.accessibilityIdentifier = "epub.reader.bookmarks.empty"
emptyStateLabel.isAccessibilityElement = true
tableView.addSubview(emptyStateLabel)
NSLayoutConstraint.activate([
emptyStateLabel.centerXAnchor.constraint(equalTo: tableView.frameLayoutGuide.centerXAnchor),
emptyStateLabel.centerYAnchor.constraint(equalTo: tableView.frameLayoutGuide.centerYAnchor),
emptyStateLabel.leadingAnchor.constraint(greaterThanOrEqualTo: tableView.frameLayoutGuide.leadingAnchor, constant: 24),
emptyStateLabel.trailingAnchor.constraint(lessThanOrEqualTo: tableView.frameLayoutGuide.trailingAnchor, constant: -24)
])
}
private func updateEmptyState() {
guard bookmarks.isEmpty == false else {
let label = UILabel()
label.text = "暂无书签"
label.textAlignment = .center
label.textColor = theme.contentTextColor.withAlphaComponent(0.7)
label.numberOfLines = 0
label.accessibilityIdentifier = "epub.reader.bookmarks.empty"
tableView.backgroundView = label
return
}
tableView.backgroundView = nil
installEmptyStateLabelIfNeeded()
emptyStateLabel.textColor = theme.contentTextColor.withAlphaComponent(0.7)
emptyStateLabel.isHidden = !bookmarks.isEmpty
}
private func titleText(for bookmark: RDEPUBBookmark) -> String {