refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import UIKit
|
||||
|
||||
final class RDEPUBNotePopupCoordinator {
|
||||
|
||||
static func present(
|
||||
_ note: RDEPUBResolvedNote,
|
||||
from controller: UIViewController,
|
||||
onReturnToSource: (() -> Void)? = nil,
|
||||
onOpenNoteLocation: (() -> Void)? = nil
|
||||
) {
|
||||
let popup = RDEPUBNotePopupViewController(
|
||||
note: note,
|
||||
onReturnToSource: onReturnToSource,
|
||||
onOpenNoteLocation: onOpenNoteLocation
|
||||
)
|
||||
let navigationController = UINavigationController(rootViewController: popup)
|
||||
navigationController.modalPresentationStyle = .pageSheet
|
||||
if let sheet = navigationController.sheetPresentationController {
|
||||
sheet.detents = [.medium(), .large()]
|
||||
sheet.prefersGrabberVisible = true
|
||||
}
|
||||
controller.present(navigationController, animated: true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
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?()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user