- 实现EPUB阅读器搜索功能及选中注释功能 - 优化CFI模块,修复代码审查发现的11个问题 - 实现大书远距目录跳转与后台补全优化方案 - 优化设置面板与章节运行时联动 - 重构及大量改进优化
146 lines
5.3 KiB
Swift
146 lines
5.3 KiB
Swift
import UIKit
|
|
|
|
final class RDEPUBNotePopupViewController: UIViewController {
|
|
|
|
private let note: RDEPUBResolvedNote
|
|
private let onReturnToSource: (() -> Void)?
|
|
private let onOpenNoteLocation: (() -> Void)?
|
|
|
|
private let textView = UITextView()
|
|
private let actionsStackView = UIStackView()
|
|
|
|
init(
|
|
note: RDEPUBResolvedNote,
|
|
onReturnToSource: (() -> Void)? = nil,
|
|
onOpenNoteLocation: (() -> Void)? = nil
|
|
) {
|
|
self.note = note
|
|
self.onReturnToSource = onReturnToSource
|
|
self.onOpenNoteLocation = onOpenNoteLocation
|
|
super.init(nibName: nil, bundle: nil)
|
|
title = note.title ?? "注释"
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
view.backgroundColor = .systemBackground
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
|
barButtonSystemItem: .done,
|
|
target: self,
|
|
action: #selector(close)
|
|
)
|
|
|
|
textView.translatesAutoresizingMaskIntoConstraints = false
|
|
textView.isEditable = false
|
|
textView.alwaysBounceVertical = true
|
|
textView.backgroundColor = .clear
|
|
textView.textContainerInset = UIEdgeInsets(top: 18, left: 18, bottom: 24, right: 18)
|
|
textView.attributedText = attributedContent()
|
|
textView.accessibilityIdentifier = "epub.reader.note.text"
|
|
actionsStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
actionsStackView.axis = .horizontal
|
|
actionsStackView.spacing = 12
|
|
actionsStackView.distribution = .fillEqually
|
|
actionsStackView.accessibilityIdentifier = "epub.reader.note.actions"
|
|
configureActionButtons()
|
|
|
|
view.addSubview(actionsStackView)
|
|
view.addSubview(textView)
|
|
|
|
NSLayoutConstraint.activate([
|
|
actionsStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 18),
|
|
actionsStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -18),
|
|
actionsStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
|
|
actionsStackView.heightAnchor.constraint(equalToConstant: actionsStackView.arrangedSubviews.isEmpty ? 0 : 36),
|
|
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
textView.topAnchor.constraint(equalTo: actionsStackView.bottomAnchor, constant: actionsStackView.arrangedSubviews.isEmpty ? 0 : 8),
|
|
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
])
|
|
}
|
|
|
|
private func configureActionButtons() {
|
|
if note.sourceLocation != nil, onReturnToSource != nil {
|
|
actionsStackView.addArrangedSubview(
|
|
makeActionButton(title: "返回原文", action: #selector(returnToSource))
|
|
)
|
|
}
|
|
if onOpenNoteLocation != nil {
|
|
actionsStackView.addArrangedSubview(
|
|
makeActionButton(title: "打开注释原文", action: #selector(openNoteLocation))
|
|
)
|
|
}
|
|
actionsStackView.isHidden = actionsStackView.arrangedSubviews.isEmpty
|
|
}
|
|
|
|
private func makeActionButton(title: String, action: Selector) -> UIButton {
|
|
let button = UIButton(type: .system)
|
|
var configuration = UIButton.Configuration.filled()
|
|
configuration.cornerStyle = .medium
|
|
configuration.title = title
|
|
configuration.baseBackgroundColor = .secondarySystemBackground
|
|
configuration.baseForegroundColor = .label
|
|
button.configuration = configuration
|
|
if title == "返回原文" {
|
|
button.accessibilityIdentifier = "epub.reader.note.return"
|
|
} else if title == "打开注释原文" {
|
|
button.accessibilityIdentifier = "epub.reader.note.open"
|
|
}
|
|
button.addTarget(self, action: action, for: .touchUpInside)
|
|
return button
|
|
}
|
|
|
|
private func attributedContent() -> NSAttributedString {
|
|
let html = """
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<style>
|
|
body {
|
|
font: -apple-system-body;
|
|
color: #1f1f1f;
|
|
line-height: 1.55;
|
|
}
|
|
a { color: #3b6ea8; }
|
|
img, svg, table { max-width: 100%; height: auto; }
|
|
</style>
|
|
</head>
|
|
<body>\(note.html)</body>
|
|
</html>
|
|
"""
|
|
guard let data = html.data(using: .utf8),
|
|
let attributed = try? NSMutableAttributedString(
|
|
data: data,
|
|
options: [
|
|
.documentType: NSAttributedString.DocumentType.html,
|
|
.characterEncoding: String.Encoding.utf8.rawValue
|
|
],
|
|
documentAttributes: nil
|
|
) else {
|
|
return NSAttributedString(string: note.plainText)
|
|
}
|
|
return attributed
|
|
}
|
|
|
|
@objc private func close() {
|
|
dismiss(animated: true)
|
|
}
|
|
|
|
@objc private func returnToSource() {
|
|
dismiss(animated: true) { [onReturnToSource] in
|
|
onReturnToSource?()
|
|
}
|
|
}
|
|
|
|
@objc private func openNoteLocation() {
|
|
dismiss(animated: true) { [onOpenNoteLocation] in
|
|
onOpenNoteLocation?()
|
|
}
|
|
}
|
|
}
|