Compare commits

..

No commits in common. "72c1f8d89c34745e7e86788605c58fc60af67e25" and "8ccb7157b28a95097f148724b167e62a48208e6a" have entirely different histories.

28 changed files with 318 additions and 183 deletions

View File

@ -7,8 +7,8 @@
## 语言与工程约束 ## 语言与工程约束
- **主要语言**SwiftPodspec 声明 `s.swift_versions = ["5.10"]`,见 `RDEpubReaderView.podspec` - **主要语言**SwiftPodspec 声明 `s.swift_versions = ["5.10"]`,见 `RDEpubReaderView.podspec`
- **最低系统版本**Podspec `iOS 15.0``RDEpubReaderView.podspec`),示例工程 Podfile/构建设置里常见为 `iOS 15.6``ReadViewDemo/Podfile` - **最低系统版本**Podspec `iOS 15.0``RDEpubReaderView.podspec`),示例工程 Podfile/构建设置里常见为 `iOS 15.6``Podfile`
- **依赖管理**CocoaPods`ReadViewDemo/Podfile`、`ReadViewDemo/Podfile.lock` - **依赖管理**CocoaPods`Podfile`、`ReadViewDemo/Podfile`、`Podfile.lock`
## 命名约定 ## 命名约定
@ -76,7 +76,7 @@
## Evidence关键证据文件 ## Evidence关键证据文件
- `CONTEXT.md` - `CONTEXT.md`
- `ReadViewDemo/Podfile` - `Podfile`
- `RDEpubReaderView.podspec` - `RDEpubReaderView.podspec`
- `ReadViewDemo/ReadViewDemo/ViewController.swift` - `ReadViewDemo/ReadViewDemo/ViewController.swift`
- `Sources/RDEpubReaderView/ReaderView/RDEpubReaderView.swift` - `Sources/RDEpubReaderView/ReaderView/RDEpubReaderView.swift`

View File

@ -64,13 +64,13 @@
### CocoaPods 依赖准备 ### CocoaPods 依赖准备
> 仓库包含示例工程 `ReadViewDemo`,并已提交 `ReadViewDemo/Pods/` 与 `ReadViewDemo/Podfile.lock`。如本地环境未同步,请在示例工程目录运行: > 仓库包含示例工程 `ReadViewDemo`,并已提交 `Pods/` 与 `Podfile.lock`。如本地环境未同步,可在仓库根或示例工程目录运行:
```bash ```bash
cd ReadViewDemo && pod install pod install
``` ```
(依赖入口:`ReadViewDemo/Podfile` (依赖入口:`Podfile`、`ReadViewDemo/Podfile`
### 运行 UI 测试 ### 运行 UI 测试

30
Podfile Normal file
View File

@ -0,0 +1,30 @@
platform :ios, '15.6'
use_frameworks!
workspace 'ReadViewSDK.xcworkspace'
project 'ReadViewSDK.xcodeproj'
target 'ReadViewSDK' do
pod 'RDEpubReaderView', :path => '..'
end
post_install do |installer|
apply_settings = lambda do |config|
config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.6'
end
# Pods project (Pods.xcodeproj)
installer.pods_project.build_configurations.each { |c| apply_settings.call(c) }
installer.pods_project.targets.each { |t| t.build_configurations.each { |c| apply_settings.call(c) } }
installer.aggregate_targets.each do |at|
p = at.user_project
next unless p
# User project(s) that integrate Pods (e.g. ReadViewDemo / ReadViewSDK)
p.build_configurations.each { |c| apply_settings.call(c) }
p.targets.each { |t| t.build_configurations.each { |c| apply_settings.call(c) } }
p.save
end
end

View File

@ -3,7 +3,7 @@ platform :ios, '15.6'
target 'ReadViewDemo' do target 'ReadViewDemo' do
# Comment the next line if you don't want to use dynamic frameworks # Comment the next line if you don't want to use dynamic frameworks
use_frameworks! use_frameworks!
pod 'RDEpubReaderView', :path => '../Sources/RDEpubReaderView' pod 'RDEpubReaderView', :path => '..'
end end

View File

@ -16,6 +16,7 @@ enum IDs {
static let readerToc = "epub.reader.toc" static let readerToc = "epub.reader.toc"
static let readerBookmarks = "epub.reader.bookmarks" static let readerBookmarks = "epub.reader.bookmarks"
static let readerHighlights = "epub.reader.highlights" static let readerHighlights = "epub.reader.highlights"
static let readerAddHighlight = "epub.reader.add-highlight"
static let readerSettings = "epub.reader.settings" static let readerSettings = "epub.reader.settings"
static let readerTocPanel = "epub.reader.toc.panel" static let readerTocPanel = "epub.reader.toc.panel"
static let readerTocTable = "epub.reader.toc.table" static let readerTocTable = "epub.reader.toc.table"

View File

@ -7,6 +7,15 @@ final class ReaderAnnotationExtendedTests: XCTestCase {
continueAfterFailure = false continueAfterFailure = false
} }
func testBottomToolbarHasAddHighlightButton() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let addHighlightButton = app.buttons[IDs.readerAddHighlight]
XCTAssertTrue(addHighlightButton.waitForExistence(timeout: 5), "底部工具栏标注按钮未出现")
}
func testBottomToolbarHasHighlightsButton() { func testBottomToolbarHasHighlightsButton() {
app.launchAndOpenSampleBook() app.launchAndOpenSampleBook()
app.waitForReader() app.waitForReader()
@ -23,7 +32,8 @@ final class ReaderAnnotationExtendedTests: XCTestCase {
XCTAssertTrue(app.buttons[IDs.readerToc].waitForExistence(timeout: 5), "目录按钮未出现") XCTAssertTrue(app.buttons[IDs.readerToc].waitForExistence(timeout: 5), "目录按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerBookmarks].exists, "书签按钮未出现") XCTAssertTrue(app.buttons[IDs.readerBookmarks].exists, "书签按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerHighlights].exists, "标注列表按钮未出现") XCTAssertTrue(app.buttons[IDs.readerHighlights].exists, "高亮按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerAddHighlight].exists, "标注按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerSettings].exists, "设置按钮未出现") XCTAssertTrue(app.buttons[IDs.readerSettings].exists, "设置按钮未出现")
} }

View File

@ -38,6 +38,25 @@ final class ToolbarStateTests: XCTestCase {
app.waitForDemoReaderState(timeout: 5, description: "reader=opened") { $0.isOpened } app.waitForDemoReaderState(timeout: 5, description: "reader=opened") { $0.isOpened }
} }
func testAddHighlightButtonDisabledWithoutSelection() throws {
app.launchAndOpenSampleBook()
app.waitForReader(timeout: 12)
app.showReaderChromeIfNeeded()
let addHighlightButton = app.buttons[IDs.readerAddHighlight]
XCTAssertTrue(addHighlightButton.waitForExistence(timeout: 3), "标注按钮应存在")
let beforeState = app.waitForDemoReaderState(timeout: 3, description: "初始高亮数") { $0.highlights != nil }
let highlightsBefore = beforeState.highlights ?? 0
addHighlightButton.tap()
let afterState = app.waitForDemoReaderState(timeout: 3, description: "点击后的高亮数") { $0.highlights != nil }
let highlightsAfter = afterState.highlights ?? 0
XCTAssertEqual(highlightsAfter, highlightsBefore, "无选区时点击标注按钮不应创建标注")
}
func testPageCurlSwipeNavigation() throws { func testPageCurlSwipeNavigation() throws {
app.launchAndOpenSampleBook(displayType: "pagecurl") app.launchAndOpenSampleBook(displayType: "pagecurl")
app.waitForReader(timeout: 12) app.waitForReader(timeout: 12)

View File

@ -136,9 +136,6 @@ struct RDEPUBStyleSheetComposer {
private static func darkCSS(style: RDEPUBTextRenderStyle) -> String { private static func darkCSS(style: RDEPUBTextRenderStyle) -> String {
let background = style.backgroundColor?.rd_cssString ?? "rgba(0, 0, 0, 1.000)" let background = style.backgroundColor?.rd_cssString ?? "rgba(0, 0, 0, 1.000)"
let text = style.textColor?.rd_cssString ?? "rgba(255, 255, 255, 1.000)" let text = style.textColor?.rd_cssString ?? "rgba(255, 255, 255, 1.000)"
// Keep the established dark-theme compatibility rules, but explicitly
// clear the universal background that they would otherwise apply to image
// attachments when converted into attributed strings by DTCoreText.
return RDEPUBAssetRepository.string(for: .wxReadDarkCSS) + "\n\n" + """ return RDEPUBAssetRepository.string(for: .wxReadDarkCSS) + "\n\n" + """
html, body { html, body {
background: \(background) !important; background: \(background) !important;
@ -147,9 +144,6 @@ struct RDEPUBStyleSheetComposer {
a { a {
color: \(text) !important; color: \(text) !important;
} }
img, video {
background-color: transparent !important;
}
""" """
} }

View File

@ -4,14 +4,12 @@ final class RDEPUBNotePopupCoordinator {
static func present( static func present(
_ note: RDEPUBResolvedNote, _ note: RDEPUBResolvedNote,
theme: RDEPUBReaderTheme,
from controller: UIViewController, from controller: UIViewController,
onReturnToSource: (() -> Void)? = nil, onReturnToSource: (() -> Void)? = nil,
onOpenNoteLocation: (() -> Void)? = nil onOpenNoteLocation: (() -> Void)? = nil
) { ) {
let popup = RDEPUBNotePopupViewController( let popup = RDEPUBNotePopupViewController(
note: note, note: note,
theme: theme,
onReturnToSource: onReturnToSource, onReturnToSource: onReturnToSource,
onOpenNoteLocation: onOpenNoteLocation onOpenNoteLocation: onOpenNoteLocation
) )

View File

@ -3,7 +3,6 @@ import UIKit
final class RDEPUBNotePopupViewController: UIViewController { final class RDEPUBNotePopupViewController: UIViewController {
private let note: RDEPUBResolvedNote private let note: RDEPUBResolvedNote
private let theme: RDEPUBReaderTheme
private let onReturnToSource: (() -> Void)? private let onReturnToSource: (() -> Void)?
private let onOpenNoteLocation: (() -> Void)? private let onOpenNoteLocation: (() -> Void)?
@ -12,12 +11,10 @@ final class RDEPUBNotePopupViewController: UIViewController {
init( init(
note: RDEPUBResolvedNote, note: RDEPUBResolvedNote,
theme: RDEPUBReaderTheme,
onReturnToSource: (() -> Void)? = nil, onReturnToSource: (() -> Void)? = nil,
onOpenNoteLocation: (() -> Void)? = nil onOpenNoteLocation: (() -> Void)? = nil
) { ) {
self.note = note self.note = note
self.theme = theme
self.onReturnToSource = onReturnToSource self.onReturnToSource = onReturnToSource
self.onOpenNoteLocation = onOpenNoteLocation self.onOpenNoteLocation = onOpenNoteLocation
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
@ -31,7 +28,7 @@ final class RDEPUBNotePopupViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
view.backgroundColor = theme.toolBackgroundColor view.backgroundColor = .systemBackground
navigationItem.rightBarButtonItem = UIBarButtonItem( navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .done, barButtonSystemItem: .done,
target: self, target: self,
@ -86,8 +83,8 @@ final class RDEPUBNotePopupViewController: UIViewController {
var configuration = UIButton.Configuration.filled() var configuration = UIButton.Configuration.filled()
configuration.cornerStyle = .medium configuration.cornerStyle = .medium
configuration.title = title configuration.title = title
configuration.baseBackgroundColor = theme.toolControlTextColor.withAlphaComponent(0.10) configuration.baseBackgroundColor = .secondarySystemBackground
configuration.baseForegroundColor = theme.toolControlTextColor configuration.baseForegroundColor = .label
button.configuration = configuration button.configuration = configuration
if title == "返回原文" { if title == "返回原文" {
button.accessibilityIdentifier = "epub.reader.note.return" button.accessibilityIdentifier = "epub.reader.note.return"
@ -106,7 +103,7 @@ final class RDEPUBNotePopupViewController: UIViewController {
<style> <style>
body { body {
font: -apple-system-body; font: -apple-system-body;
color: \(theme.themeTextColorCSS); color: #1f1f1f;
line-height: 1.55; line-height: 1.55;
} }
a { color: #3b6ea8; } a { color: #3b6ea8; }

View File

@ -4,20 +4,13 @@ final class RDEPUBAnnotationEditorViewController: UIViewController, UITextViewDe
private let quote: String private let quote: String
private let initialNote: String? private let initialNote: String?
private let theme: RDEPUBReaderTheme
private let onSave: (String?) -> Void private let onSave: (String?) -> Void
private let textView = UITextView() private let textView = UITextView()
private let countLabel = UILabel() private let countLabel = UILabel()
init( init(quote: String, initialNote: String?, onSave: @escaping (String?) -> Void) {
quote: String,
initialNote: String?,
theme: RDEPUBReaderTheme,
onSave: @escaping (String?) -> Void
) {
self.quote = quote self.quote = quote
self.initialNote = initialNote self.initialNote = initialNote
self.theme = theme
self.onSave = onSave self.onSave = onSave
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
title = "写注释" title = "写注释"
@ -29,7 +22,7 @@ final class RDEPUBAnnotationEditorViewController: UIViewController, UITextViewDe
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
view.backgroundColor = theme.toolBackgroundColor view.backgroundColor = UIColor(red: 0.975, green: 0.965, blue: 0.935, alpha: 1)
configureNavigation() configureNavigation()
configureContent() configureContent()
updateCount() updateCount()
@ -43,9 +36,9 @@ final class RDEPUBAnnotationEditorViewController: UIViewController, UITextViewDe
navigationItem.rightBarButtonItem = UIBarButtonItem( navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "保存", style: .done, target: self, action: #selector(saveAction) title: "保存", style: .done, target: self, action: #selector(saveAction)
) )
navigationController?.navigationBar.tintColor = .systemBlue navigationController?.navigationBar.tintColor = UIColor(red: 0.10, green: 0.34, blue: 0.78, alpha: 1)
navigationController?.navigationBar.titleTextAttributes = [ navigationController?.navigationBar.titleTextAttributes = [
.foregroundColor: theme.toolControlTextColor, .foregroundColor: UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1),
.font: UIFont.systemFont(ofSize: 17, weight: .semibold) .font: UIFont.systemFont(ofSize: 17, weight: .semibold)
] ]
} }
@ -66,16 +59,16 @@ final class RDEPUBAnnotationEditorViewController: UIViewController, UITextViewDe
inputCard.addSubview(textView) inputCard.addSubview(textView)
inputCard.addSubview(countLabel) inputCard.addSubview(countLabel)
quoteCard.backgroundColor = theme.contentBackgroundColor.withAlphaComponent(0.78) quoteCard.backgroundColor = UIColor.white.withAlphaComponent(0.78)
quoteCard.layer.cornerRadius = 16 quoteCard.layer.cornerRadius = 16
rail.backgroundColor = UIColor(red: 0.94, green: 0.63, blue: 0.18, alpha: 1) rail.backgroundColor = UIColor(red: 0.94, green: 0.63, blue: 0.18, alpha: 1)
rail.layer.cornerRadius = 2 rail.layer.cornerRadius = 2
quoteLabel.text = quote quoteLabel.text = quote
quoteLabel.numberOfLines = 5 quoteLabel.numberOfLines = 5
quoteLabel.font = UIFont.systemFont(ofSize: 16) quoteLabel.font = UIFont.systemFont(ofSize: 16)
quoteLabel.textColor = theme.contentTextColor.withAlphaComponent(0.85) quoteLabel.textColor = UIColor(red: 0.19, green: 0.20, blue: 0.23, alpha: 1)
inputCard.backgroundColor = theme.contentBackgroundColor inputCard.backgroundColor = .white
inputCard.layer.cornerRadius = 18 inputCard.layer.cornerRadius = 18
inputCard.layer.shadowColor = UIColor.black.cgColor inputCard.layer.shadowColor = UIColor.black.cgColor
inputCard.layer.shadowOpacity = 0.06 inputCard.layer.shadowOpacity = 0.06
@ -84,7 +77,7 @@ final class RDEPUBAnnotationEditorViewController: UIViewController, UITextViewDe
textView.text = initialNote textView.text = initialNote
textView.delegate = self textView.delegate = self
textView.font = UIFont.systemFont(ofSize: 17) textView.font = UIFont.systemFont(ofSize: 17)
textView.textColor = theme.contentTextColor textView.textColor = UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)
textView.backgroundColor = .clear textView.backgroundColor = .clear
textView.accessibilityIdentifier = "epub.reader.annotation.editor" textView.accessibilityIdentifier = "epub.reader.annotation.editor"
countLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: .regular) countLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: .regular)

View File

@ -8,7 +8,6 @@ final class RDEPUBReaderBottomToolView: RDEPUBReaderToolView, RDEPUBReaderBottom
var onShowHighlights: (() -> Void)? var onShowHighlights: (() -> Void)?
@available(*, deprecated, message: "Use the text selection menu to create annotations.")
var onAddHighlight: (() -> Void)? var onAddHighlight: (() -> Void)?
var onShowSettings: (() -> Void)? var onShowSettings: (() -> Void)?
@ -26,8 +25,9 @@ final class RDEPUBReaderBottomToolView: RDEPUBReaderToolView, RDEPUBReaderBottom
private let bookmarksButton = RDEPUBReaderTintButton(type: .system) private let bookmarksButton = RDEPUBReaderTintButton(type: .system)
/// 线 / private let highlightsButton = RDEPUBReaderTintButton(type: .system)
private let annotationListButton = RDEPUBReaderTintButton(type: .system)
private let addHighlightButton = RDEPUBReaderTintButton(type: .system)
private let settingsButton = RDEPUBReaderTintButton(type: .system) private let settingsButton = RDEPUBReaderTintButton(type: .system)
@ -39,7 +39,8 @@ final class RDEPUBReaderBottomToolView: RDEPUBReaderToolView, RDEPUBReaderBottom
stackView.translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(chapterButton) stackView.addArrangedSubview(chapterButton)
stackView.addArrangedSubview(bookmarksButton) stackView.addArrangedSubview(bookmarksButton)
stackView.addArrangedSubview(annotationListButton) stackView.addArrangedSubview(highlightsButton)
stackView.addArrangedSubview(addHighlightButton)
stackView.addArrangedSubview(settingsButton) stackView.addArrangedSubview(settingsButton)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@ -51,20 +52,23 @@ final class RDEPUBReaderBottomToolView: RDEPUBReaderToolView, RDEPUBReaderBottom
configureButton(chapterButton, systemName: "list.bullet", fallbackTitle: "目录") configureButton(chapterButton, systemName: "list.bullet", fallbackTitle: "目录")
configureButton(bookmarksButton, systemName: "bookmark", fallbackTitle: "书签") configureButton(bookmarksButton, systemName: "bookmark", fallbackTitle: "书签")
configureButton(annotationListButton, systemName: "note.text", fallbackTitle: "标注") configureButton(highlightsButton, systemName: "note.text", fallbackTitle: "标注")
configureButton(addHighlightButton, systemName: "highlighter", fallbackTitle: "标注")
configureButton(settingsButton, systemName: "textformat.size", fallbackTitle: "设置") configureButton(settingsButton, systemName: "textformat.size", fallbackTitle: "设置")
chapterButton.accessibilityIdentifier = "epub.reader.toc" chapterButton.accessibilityIdentifier = "epub.reader.toc"
bookmarksButton.accessibilityIdentifier = "epub.reader.bookmarks" bookmarksButton.accessibilityIdentifier = "epub.reader.bookmarks"
annotationListButton.accessibilityIdentifier = "epub.reader.highlights" highlightsButton.accessibilityIdentifier = "epub.reader.highlights"
addHighlightButton.accessibilityIdentifier = "epub.reader.add-highlight"
settingsButton.accessibilityIdentifier = "epub.reader.settings" settingsButton.accessibilityIdentifier = "epub.reader.settings"
[chapterButton, bookmarksButton, annotationListButton, settingsButton].forEach { button in [chapterButton, bookmarksButton, highlightsButton, addHighlightButton, settingsButton].forEach { button in
button.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true button.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
} }
chapterButton.addTarget(self, action: #selector(chapterAction), for: .touchUpInside) chapterButton.addTarget(self, action: #selector(chapterAction), for: .touchUpInside)
bookmarksButton.addTarget(self, action: #selector(bookmarksAction), for: .touchUpInside) bookmarksButton.addTarget(self, action: #selector(bookmarksAction), for: .touchUpInside)
annotationListButton.addTarget(self, action: #selector(annotationListAction), for: .touchUpInside) highlightsButton.addTarget(self, action: #selector(highlightsAction), for: .touchUpInside)
addHighlightButton.addTarget(self, action: #selector(highlightAction), for: .touchUpInside)
settingsButton.addTarget(self, action: #selector(settingsAction), for: .touchUpInside) settingsButton.addTarget(self, action: #selector(settingsAction), for: .touchUpInside)
} }
@ -78,21 +82,20 @@ final class RDEPUBReaderBottomToolView: RDEPUBReaderToolView, RDEPUBReaderBottom
override func apply(theme: RDEPUBReaderTheme) { override func apply(theme: RDEPUBReaderTheme) {
super.apply(theme: theme) super.apply(theme: theme)
[chapterButton, bookmarksButton, annotationListButton, settingsButton].forEach { button in [chapterButton, bookmarksButton, highlightsButton, addHighlightButton, settingsButton].forEach { button in
button.tintColor = theme.toolControlTextColor button.tintColor = .black
button.setTitleColor(theme.toolControlTextColor, for: .normal) button.setTitleColor(.black, for: .normal)
} }
} }
func setHighlightsEnabled(_ isEnabled: Bool) { func setAddHighlightEnabled(_ isEnabled: Bool) {
annotationListButton.isEnabled = isEnabled addHighlightButton.isEnabled = isEnabled
annotationListButton.alpha = isEnabled ? 1 : 0.45 addHighlightButton.alpha = isEnabled ? 1 : 0.45
} }
@available(*, deprecated, message: "Use the text selection menu to create annotations.") func setHighlightsEnabled(_ isEnabled: Bool) {
func setAddHighlightEnabled(_ isEnabled: Bool) { highlightsButton.isEnabled = isEnabled
// The built-in toolbar no longer exposes this action. Keep the method so highlightsButton.alpha = isEnabled ? 1 : 0.45
// custom toolbar integrations compiled against previous SDKs remain valid.
} }
func setBookmarksEnabled(_ isEnabled: Bool) { func setBookmarksEnabled(_ isEnabled: Bool) {
@ -107,7 +110,8 @@ final class RDEPUBReaderBottomToolView: RDEPUBReaderToolView, RDEPUBReaderBottom
) { ) {
chapterButton.isHidden = !showsTableOfContents chapterButton.isHidden = !showsTableOfContents
bookmarksButton.isHidden = false bookmarksButton.isHidden = false
annotationListButton.isHidden = !allowsHighlights highlightsButton.isHidden = !allowsHighlights
addHighlightButton.isHidden = !allowsHighlights
settingsButton.isHidden = !showsSettingsPanel settingsButton.isHidden = !showsSettingsPanel
} }
@ -128,10 +132,14 @@ final class RDEPUBReaderBottomToolView: RDEPUBReaderToolView, RDEPUBReaderBottom
onShowBookmarks?() onShowBookmarks?()
} }
@objc private func annotationListAction() { @objc private func highlightsAction() {
onShowHighlights?() onShowHighlights?()
} }
@objc private func highlightAction() {
onAddHighlight?()
}
@objc private func settingsAction() { @objc private func settingsAction() {
onShowSettings?() onShowSettings?()
} }

View File

@ -32,7 +32,7 @@ final class RDEPUBReaderChapterListController: UITableViewController {
tableView.accessibilityIdentifier = "epub.reader.toc.table" tableView.accessibilityIdentifier = "epub.reader.toc.table"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.tableFooterView = UIView(frame: .zero) tableView.tableFooterView = UIView(frame: .zero)
tableView.backgroundColor = theme.toolBackgroundColor tableView.backgroundColor = theme.contentBackgroundColor
navigationController?.navigationBar.tintColor = theme.toolControlTextColor navigationController?.navigationBar.tintColor = theme.toolControlTextColor
navigationController?.navigationBar.barTintColor = theme.toolBackgroundColor navigationController?.navigationBar.barTintColor = theme.toolBackgroundColor
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor] navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor]
@ -46,7 +46,7 @@ final class RDEPUBReaderChapterListController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row] let item = items[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.backgroundColor = theme.toolBackgroundColor cell.backgroundColor = theme.contentBackgroundColor
cell.textLabel?.numberOfLines = 2 cell.textLabel?.numberOfLines = 2
cell.textLabel?.text = item.title cell.textLabel?.text = item.title
if isCurrentItem(item) { if isCurrentItem(item) {

View File

@ -154,7 +154,6 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
RDEPUBNotePopupCoordinator.present( RDEPUBNotePopupCoordinator.present(
note, note,
theme: configuration.theme,
from: self, from: self,
onReturnToSource: { [weak self] in onReturnToSource: { [weak self] in
guard let self, let sourceLocation = note.sourceLocation else { return } guard let self, let sourceLocation = note.sourceLocation else { return }

View File

@ -130,6 +130,10 @@ extension RDEPUBReaderController {
runtime.presentHighlightsManager() runtime.presentHighlightsManager()
} }
func presentAnnotationCreation() {
runtime.presentAnnotationCreation()
}
func handleSelectionMenuAction(_ action: RDEPUBAnnotationMenuAction, selection: RDEPUBSelection?) { func handleSelectionMenuAction(_ action: RDEPUBAnnotationMenuAction, selection: RDEPUBSelection?) {
runtime.handleSelectionMenuAction(action, selection: selection) runtime.handleSelectionMenuAction(action, selection: selection)
} }

View File

@ -68,15 +68,15 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
let highlight = filteredHighlights[indexPath.row] let highlight = filteredHighlights[indexPath.row]
cell.backgroundColor = .clear cell.backgroundColor = .clear
cell.contentView.backgroundColor = theme.contentBackgroundColor.withAlphaComponent(0.76) cell.contentView.backgroundColor = UIColor.white.withAlphaComponent(0.76)
cell.contentView.layer.cornerRadius = 14 cell.contentView.layer.cornerRadius = 14
cell.contentView.layer.masksToBounds = true cell.contentView.layer.masksToBounds = true
cell.textLabel?.textColor = theme.contentTextColor cell.textLabel?.textColor = UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)
cell.textLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium) cell.textLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
cell.textLabel?.numberOfLines = 2 cell.textLabel?.numberOfLines = 2
cell.textLabel?.text = titleText(for: highlight) cell.textLabel?.text = titleText(for: highlight)
cell.detailTextLabel?.textColor = theme.contentTextColor.withAlphaComponent(0.65) cell.detailTextLabel?.textColor = UIColor(red: 0.34, green: 0.36, blue: 0.40, alpha: 1)
cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12) cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12)
cell.detailTextLabel?.numberOfLines = 3 cell.detailTextLabel?.numberOfLines = 3
cell.detailTextLabel?.text = detailText(for: highlight) cell.detailTextLabel?.text = detailText(for: highlight)
@ -113,10 +113,10 @@ final class RDEPUBReaderHighlightsViewController: UITableViewController {
} }
private func applyTheme() { private func applyTheme() {
tableView.backgroundColor = theme.toolBackgroundColor tableView.backgroundColor = UIColor(red: 0.975, green: 0.965, blue: 0.935, alpha: 1)
navigationController?.navigationBar.tintColor = .systemBlue navigationController?.navigationBar.tintColor = UIColor(red: 0.10, green: 0.34, blue: 0.78, alpha: 1)
navigationController?.navigationBar.barTintColor = tableView.backgroundColor navigationController?.navigationBar.barTintColor = tableView.backgroundColor
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor] navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)]
navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.highlights.navbar" navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.highlights.navbar"
} }
@ -243,15 +243,15 @@ final class RDEPUBReaderBookmarksViewController: UITableViewController {
let bookmark = bookmarks[indexPath.row] let bookmark = bookmarks[indexPath.row]
cell.backgroundColor = .clear cell.backgroundColor = .clear
cell.contentView.backgroundColor = theme.contentBackgroundColor.withAlphaComponent(0.76) cell.contentView.backgroundColor = UIColor.white.withAlphaComponent(0.76)
cell.contentView.layer.cornerRadius = 14 cell.contentView.layer.cornerRadius = 14
cell.contentView.layer.masksToBounds = true cell.contentView.layer.masksToBounds = true
cell.textLabel?.textColor = theme.contentTextColor cell.textLabel?.textColor = UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)
cell.textLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium) cell.textLabel?.font = UIFont.systemFont(ofSize: 15, weight: .medium)
cell.textLabel?.numberOfLines = 2 cell.textLabel?.numberOfLines = 2
cell.textLabel?.text = titleText(for: bookmark) cell.textLabel?.text = titleText(for: bookmark)
cell.detailTextLabel?.textColor = theme.contentTextColor.withAlphaComponent(0.65) cell.detailTextLabel?.textColor = UIColor(red: 0.34, green: 0.36, blue: 0.40, alpha: 1)
cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12) cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12)
cell.detailTextLabel?.numberOfLines = 3 cell.detailTextLabel?.numberOfLines = 3
cell.detailTextLabel?.text = detailText(for: bookmark) cell.detailTextLabel?.text = detailText(for: bookmark)
@ -272,10 +272,10 @@ final class RDEPUBReaderBookmarksViewController: UITableViewController {
} }
private func applyTheme() { private func applyTheme() {
tableView.backgroundColor = theme.toolBackgroundColor tableView.backgroundColor = UIColor(red: 0.975, green: 0.965, blue: 0.935, alpha: 1)
navigationController?.navigationBar.tintColor = .systemBlue navigationController?.navigationBar.tintColor = UIColor(red: 0.10, green: 0.34, blue: 0.78, alpha: 1)
navigationController?.navigationBar.barTintColor = tableView.backgroundColor navigationController?.navigationBar.barTintColor = tableView.backgroundColor
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor] navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor(red: 0.08, green: 0.10, blue: 0.14, alpha: 1)]
navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.bookmarks.navbar" navigationController?.navigationBar.accessibilityIdentifier = "epub.reader.bookmarks.navbar"
} }

View File

@ -22,8 +22,8 @@ open class RDEPUBReaderToolView: UIView {
} }
open func apply(theme: RDEPUBReaderTheme) { open func apply(theme: RDEPUBReaderTheme) {
backgroundColor = theme.toolBackgroundColor backgroundColor = .white
lineView.backgroundColor = theme.toolLineColor lineView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
} }
open func lineFrame(in bounds: CGRect) -> CGRect { open func lineFrame(in bounds: CGRect) -> CGRect {

View File

@ -37,13 +37,10 @@ public protocol RDEPUBReaderBottomToolViewProtocol: AnyObject {
/// ///
var onShowBookmarks: (() -> Void)? { get set } var onShowBookmarks: (() -> Void)? { get set }
/// 线 / ///
var onShowHighlights: (() -> Void)? { get set } var onShowHighlights: (() -> Void)? { get set }
/// / /// /
///
///
@available(*, deprecated, message: "Use the text selection menu to create annotations.")
var onAddHighlight: (() -> Void)? { get set } var onAddHighlight: (() -> Void)? { get set }
/// ///
@ -58,13 +55,10 @@ public protocol RDEPUBReaderBottomToolViewProtocol: AnyObject {
/// ///
func setBookmarksEnabled(_ isEnabled: Bool) func setBookmarksEnabled(_ isEnabled: Bool)
/// ///
///
///
@available(*, deprecated, message: "Use the text selection menu to create annotations.")
func setAddHighlightEnabled(_ isEnabled: Bool) func setAddHighlightEnabled(_ isEnabled: Bool)
/// ///
func setHighlightsEnabled(_ isEnabled: Bool) func setHighlightsEnabled(_ isEnabled: Bool)
} }

View File

@ -1,6 +1,6 @@
import UIKit import UIKit
open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolViewProtocol { public final class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolViewProtocol {
public var onBack: (() -> Void)? public var onBack: (() -> Void)?
@ -8,14 +8,9 @@ open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolVie
public var onSearch: (() -> Void)? public var onSearch: (() -> Void)?
/// 宿 /
public var onAuthorization: (() -> Void)?
private let backButton = RDEPUBReaderTintButton(type: .system) private let backButton = RDEPUBReaderTintButton(type: .system)
private let searchButton = RDEPUBReaderTintButton(type: .system) private let searchButton = RDEPUBReaderTintButton(type: .system)
private let bookmarkButton = RDEPUBReaderTintButton(type: .system) private let bookmarkButton = RDEPUBReaderTintButton(type: .system)
private let authorizationButton = RDEPUBReaderTintButton(type: .system)
private var authorizationButtonWidthConstraint: NSLayoutConstraint!
private let titleLabel: UILabel = { private let titleLabel: UILabel = {
let label = UILabel() let label = UILabel()
label.textAlignment = .center label.textAlignment = .center
@ -25,28 +20,23 @@ open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolVie
}() }()
private var isBookmarked = false private var isBookmarked = false
public override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
accessibilityIdentifier = "epub.reader.topToolbar" accessibilityIdentifier = "epub.reader.topToolbar"
self.backgroundColor = .white self.backgroundColor = .white
addSubview(backButton) addSubview(backButton)
addSubview(searchButton) addSubview(searchButton)
addSubview(bookmarkButton) addSubview(bookmarkButton)
addSubview(authorizationButton)
addSubview(titleLabel) addSubview(titleLabel)
backButton.translatesAutoresizingMaskIntoConstraints = false backButton.translatesAutoresizingMaskIntoConstraints = false
searchButton.translatesAutoresizingMaskIntoConstraints = false searchButton.translatesAutoresizingMaskIntoConstraints = false
bookmarkButton.translatesAutoresizingMaskIntoConstraints = false bookmarkButton.translatesAutoresizingMaskIntoConstraints = false
authorizationButton.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.translatesAutoresizingMaskIntoConstraints = false
backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside) backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside)
searchButton.addTarget(self, action: #selector(searchAction), for: .touchUpInside) searchButton.addTarget(self, action: #selector(searchAction), for: .touchUpInside)
bookmarkButton.addTarget(self, action: #selector(bookmarkAction), for: .touchUpInside) bookmarkButton.addTarget(self, action: #selector(bookmarkAction), for: .touchUpInside)
authorizationButton.addTarget(self, action: #selector(authorizationAction), for: .touchUpInside)
authorizationButtonWidthConstraint = authorizationButton.widthAnchor.constraint(equalToConstant: 0)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
backButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), backButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
@ -55,13 +45,7 @@ open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolVie
backButton.widthAnchor.constraint(equalToConstant: 44), backButton.widthAnchor.constraint(equalToConstant: 44),
backButton.heightAnchor.constraint(equalToConstant: 44), backButton.heightAnchor.constraint(equalToConstant: 44),
authorizationButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), bookmarkButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
authorizationButton.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 4),
authorizationButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
authorizationButton.heightAnchor.constraint(equalToConstant: 44),
authorizationButtonWidthConstraint,
bookmarkButton.trailingAnchor.constraint(equalTo: authorizationButton.leadingAnchor, constant: -4),
bookmarkButton.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 4), bookmarkButton.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 4),
bookmarkButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4), bookmarkButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
bookmarkButton.widthAnchor.constraint(equalToConstant: 44), bookmarkButton.widthAnchor.constraint(equalToConstant: 44),
@ -88,12 +72,11 @@ open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolVie
backButton.accessibilityIdentifier = "epub.reader.back" backButton.accessibilityIdentifier = "epub.reader.back"
searchButton.accessibilityIdentifier = "epub.reader.search" searchButton.accessibilityIdentifier = "epub.reader.search"
bookmarkButton.accessibilityIdentifier = "epub.reader.bookmark" bookmarkButton.accessibilityIdentifier = "epub.reader.bookmark"
authorizationButton.accessibilityIdentifier = "epub.reader.authorization"
titleLabel.accessibilityIdentifier = "epub.reader.title" titleLabel.accessibilityIdentifier = "epub.reader.title"
updateBookmarkButtonAppearance() updateBookmarkButtonAppearance()
} }
public required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@ -107,12 +90,10 @@ open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolVie
backButton.tintColor = theme.toolControlTextColor backButton.tintColor = theme.toolControlTextColor
searchButton.tintColor = theme.toolControlTextColor searchButton.tintColor = theme.toolControlTextColor
bookmarkButton.tintColor = theme.toolControlTextColor bookmarkButton.tintColor = theme.toolControlTextColor
authorizationButton.tintColor = theme.toolControlTextColor
if #unavailable(iOS 13.0) { if #unavailable(iOS 13.0) {
backButton.setTitleColor(theme.toolControlTextColor, for: .normal) backButton.setTitleColor(theme.toolControlTextColor, for: .normal)
searchButton.setTitleColor(theme.toolControlTextColor, for: .normal) searchButton.setTitleColor(theme.toolControlTextColor, for: .normal)
bookmarkButton.setTitleColor(theme.toolControlTextColor, for: .normal) bookmarkButton.setTitleColor(theme.toolControlTextColor, for: .normal)
authorizationButton.setTitleColor(theme.toolControlTextColor, for: .normal)
} }
updateBookmarkButtonAppearance() updateBookmarkButtonAppearance()
} }
@ -131,17 +112,6 @@ open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolVie
bookmarkButton.alpha = isEnabled ? 1 : 0.45 bookmarkButton.alpha = isEnabled ? 1 : 0.45
} }
/// `nil`
public func setAuthorizationButton(title: String?) {
let resolvedTitle = title?.trimmingCharacters(in: .whitespacesAndNewlines)
let isVisible = resolvedTitle?.isEmpty == false
authorizationButton.setTitle(resolvedTitle, for: .normal)
authorizationButton.isHidden = !isVisible
authorizationButton.isEnabled = isVisible
authorizationButtonWidthConstraint.constant = isVisible ? 56 : 0
authorizationButton.accessibilityLabel = resolvedTitle
}
@objc private func backAction() { @objc private func backAction() {
onBack?() onBack?()
} }
@ -154,10 +124,6 @@ open class RDEPUBReaderTopToolView: RDEPUBReaderToolView, RDEPUBReaderTopToolVie
onToggleBookmark?() onToggleBookmark?()
} }
@objc private func authorizationAction() {
onAuthorization?()
}
private func updateBookmarkButtonAppearance() { private func updateBookmarkButtonAppearance() {
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
let imageName = isBookmarked ? "bookmark.fill" : "bookmark" let imageName = isBookmarked ? "bookmark.fill" : "bookmark"

View File

@ -224,6 +224,15 @@ final class RDEPUBReaderAnnotationCoordinator {
controller.present(navigationController, animated: true) controller.present(navigationController, animated: true)
} }
func presentAnnotationCreation() {
guard let controller else { return }
guard controller.configuration.allowsHighlights,
let currentSelection = controller.currentSelection else {
return
}
presentAnnotationActionSheet(for: currentSelection)
}
func handleHighlightMenuAction(_ action: RDEPUBExistingHighlightMenuAction, highlight: RDEPUBHighlight) { func handleHighlightMenuAction(_ action: RDEPUBExistingHighlightMenuAction, highlight: RDEPUBHighlight) {
switch action { switch action {
case .copy: case .copy:
@ -468,6 +477,25 @@ final class RDEPUBReaderAnnotationCoordinator {
} }
} }
private func presentAnnotationActionSheet(for selection: RDEPUBSelection) {
guard let controller else { return }
let alert = UIAlertController(title: "创建标注", message: nil, preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "划线", style: .default) { [weak self] _ in
self?.createAnnotation(from: selection, style: .underline)
})
alert.addAction(UIAlertAction(title: "注释", style: .default) { [weak self] _ in
self?.presentAnnotationNoteEditor(for: selection)
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
if let popover = alert.popoverPresentationController {
popover.sourceView = controller.bottomToolView
popover.sourceRect = controller.bottomToolView.bounds
}
controller.present(alert, animated: true)
}
private func createAnnotation( private func createAnnotation(
from selection: RDEPUBSelection, from selection: RDEPUBSelection,
style: RDEPUBHighlightStyle, style: RDEPUBHighlightStyle,
@ -505,7 +533,6 @@ final class RDEPUBReaderAnnotationCoordinator {
let editor = RDEPUBAnnotationEditorViewController( let editor = RDEPUBAnnotationEditorViewController(
quote: quote, quote: quote,
initialNote: initialNote, initialNote: initialNote,
theme: controller.configuration.theme,
onSave: onSave onSave: onSave
) )
let navigationController = UINavigationController(rootViewController: editor) let navigationController = UINavigationController(rootViewController: editor)

View File

@ -46,6 +46,9 @@ final class RDEPUBReaderChromeCoordinator: NSObject, UIAdaptivePresentationContr
toolView.onShowHighlights = { [weak self] in toolView.onShowHighlights = { [weak self] in
self?.context.runtime?.presentHighlightsManager() self?.context.runtime?.presentHighlightsManager()
} }
toolView.onAddHighlight = { [weak self] in
self?.context.runtime?.presentAnnotationCreation()
}
toolView.onShowSettings = { [weak self] in toolView.onShowSettings = { [weak self] in
self?.presentSettings() self?.presentSettings()
} }
@ -65,6 +68,7 @@ final class RDEPUBReaderChromeCoordinator: NSObject, UIAdaptivePresentationContr
canToggleBookmark: controller.currentBookIdentifier != nil, canToggleBookmark: controller.currentBookIdentifier != nil,
hasBookmarkAtCurrentLocation: hasBookmarkAtCurrentLocation(), hasBookmarkAtCurrentLocation: hasBookmarkAtCurrentLocation(),
canShowBookmarks: !controller.activeBookmarks.isEmpty, canShowBookmarks: !controller.activeBookmarks.isEmpty,
canAddHighlight: controller.configuration.allowsHighlights && context.selectionState.hasSelection,
canShowHighlights: controller.configuration.allowsHighlights && !controller.activeHighlights.isEmpty, canShowHighlights: controller.configuration.allowsHighlights && !controller.activeHighlights.isEmpty,
showsTableOfContents: controller.configuration.showsTableOfContents, showsTableOfContents: controller.configuration.showsTableOfContents,
allowsHighlights: controller.configuration.allowsHighlights, allowsHighlights: controller.configuration.allowsHighlights,
@ -89,6 +93,7 @@ final class RDEPUBReaderChromeCoordinator: NSObject, UIAdaptivePresentationContr
showsSettingsPanel: state.showsSettingsPanel showsSettingsPanel: state.showsSettingsPanel
) )
controller.bottomToolView.setBookmarksEnabled(state.canShowBookmarks) controller.bottomToolView.setBookmarksEnabled(state.canShowBookmarks)
controller.bottomToolView.setAddHighlightEnabled(state.canAddHighlight)
controller.bottomToolView.setHighlightsEnabled(state.canShowHighlights) controller.bottomToolView.setHighlightsEnabled(state.canShowHighlights)
} }
@ -256,12 +261,11 @@ private final class RDEPUBReaderSettingsPresentationController: UIPresentationCo
let bounds = containerView.bounds let bounds = containerView.bounds
let isLandscape = bounds.width > bounds.height let isLandscape = bounds.width > bounds.height
if isLandscape { if isLandscape {
let safeBounds = bounds.inset(by: containerView.safeAreaInsets)
let width = min(390, max(300, bounds.width * 0.32)) let width = min(390, max(300, bounds.width * 0.32))
let height = min(340, safeBounds.height - 32) let height = min(340, bounds.height - 32)
return CGRect( return CGRect(
x: safeBounds.maxX - width - 16, x: bounds.maxX - width - 16,
y: safeBounds.midY - height / 2, y: bounds.midY - height / 2,
width: width, width: width,
height: height height: height
) )

View File

@ -139,10 +139,11 @@ final class RDEPUBReaderPaginationCoordinator {
return return
} }
let restoreLocation = context.currentVisibleLocation() ?? context.persistenceLocation() let restoreLocation = context.currentVisibleLocation() ?? context.persistenceLocation()
// pageCurl transitionToPage(, animated: false) fault if readerView.currentDisplayType == .pageCurl, readerView.currentPage >= 0 {
// / configure reloadData readerView.transitionToPage(pageNum: readerView.currentPage, animated: false)
// } else {
readerView.reloadData() readerView.reloadData()
}
if let restoreLocation { if let restoreLocation {
_ = context.restoreReadingLocation(restoreLocation) _ = context.restoreReadingLocation(restoreLocation)
} }

View File

@ -252,6 +252,10 @@ final class RDEPUBReaderRuntime {
annotationCoordinator.presentHighlightsManager() annotationCoordinator.presentHighlightsManager()
} }
func presentAnnotationCreation() {
annotationCoordinator.presentAnnotationCreation()
}
func handleHighlightMenuAction(_ action: RDEPUBExistingHighlightMenuAction, highlight: RDEPUBHighlight) { func handleHighlightMenuAction(_ action: RDEPUBExistingHighlightMenuAction, highlight: RDEPUBHighlight) {
annotationCoordinator.handleHighlightMenuAction(action, highlight: highlight) annotationCoordinator.handleHighlightMenuAction(action, highlight: highlight)
} }

View File

@ -8,6 +8,8 @@ struct RDEPUBReaderUIState {
let canShowBookmarks: Bool let canShowBookmarks: Bool
let canAddHighlight: Bool
let canShowHighlights: Bool let canShowHighlights: Bool
let showsTableOfContents: Bool let showsTableOfContents: Bool
@ -23,6 +25,7 @@ extension RDEPUBReaderUIState {
canToggleBookmark: false, canToggleBookmark: false,
hasBookmarkAtCurrentLocation: false, hasBookmarkAtCurrentLocation: false,
canShowBookmarks: false, canShowBookmarks: false,
canAddHighlight: false,
canShowHighlights: false, canShowHighlights: false,
showsTableOfContents: true, showsTableOfContents: true,
allowsHighlights: true, allowsHighlights: true,

View File

@ -150,7 +150,7 @@ final class RDEPUBReaderSettingsViewController: UIViewController {
handleView.widthAnchor.constraint(equalToConstant: 42), handleView.widthAnchor.constraint(equalToConstant: 42),
handleView.heightAnchor.constraint(equalToConstant: 5), handleView.heightAnchor.constraint(equalToConstant: 5),
closeButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), closeButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
closeButton.centerYAnchor.constraint(equalTo: handleView.centerYAnchor, constant: 20), closeButton.centerYAnchor.constraint(equalTo: handleView.centerYAnchor),
closeButton.widthAnchor.constraint(equalToConstant: 32), closeButton.widthAnchor.constraint(equalToConstant: 32),
closeButton.heightAnchor.constraint(equalToConstant: 32), closeButton.heightAnchor.constraint(equalToConstant: 32),

View File

@ -30,41 +30,59 @@ public struct RDEPUBReaderTheme: Equatable {
self.toolLineColor = toolLineColor self.toolLineColor = toolLineColor
} }
public static let light = preset(contentBackground: .white, text: .black) public static let light = RDEPUBReaderTheme(
contentBackgroundColor: .white,
public static let dark = preset(contentBackground: .black, text: .white) contentTextColor: .black,
toolBackgroundColor: .white,
public static let yellow = preset( toolControlTextColor: .black,
contentBackground: UIColor(red: 0.89, green: 0.87, blue: 0.79, alpha: 1), toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
text: .black toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
) )
public static let green = preset( public static let dark = RDEPUBReaderTheme(
contentBackground: UIColor(red: 0.87, green: 0.91, blue: 0.82, alpha: 1), contentBackgroundColor: .black,
text: .black contentTextColor: .white,
toolBackgroundColor: .black,
toolControlTextColor: .white,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
) )
public static let pink = preset( public static let yellow = RDEPUBReaderTheme(
contentBackground: UIColor(red: 1, green: 0.89, blue: 0.91, alpha: 1), contentBackgroundColor: UIColor(red: 0.89, green: 0.87, blue: 0.79, alpha: 1),
text: .black contentTextColor: .black,
toolBackgroundColor: UIColor(red: 0.89, green: 0.87, blue: 0.79, alpha: 1),
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
) )
public static let blue = preset( public static let green = RDEPUBReaderTheme(
contentBackground: UIColor(red: 0.8, green: 0.84, blue: 0.89, alpha: 1), contentBackgroundColor: UIColor(red: 0.87, green: 0.91, blue: 0.82, alpha: 1),
text: .black contentTextColor: .black,
toolBackgroundColor: UIColor(red: 0.87, green: 0.91, blue: 0.82, alpha: 1),
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
) )
/// / public static let pink = RDEPUBReaderTheme(
private static func preset(contentBackground: UIColor, text: UIColor) -> RDEPUBReaderTheme { contentBackgroundColor: UIColor(red: 1, green: 0.89, blue: 0.91, alpha: 1),
RDEPUBReaderTheme( contentTextColor: .black,
contentBackgroundColor: contentBackground, toolBackgroundColor: UIColor(red: 1, green: 0.89, blue: 0.91, alpha: 1),
contentTextColor: text, toolControlTextColor: .black,
toolBackgroundColor: contentBackground.rd_chromeShade, toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolControlTextColor: text, toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5), )
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
) public static let blue = RDEPUBReaderTheme(
} contentBackgroundColor: UIColor(red: 0.8, green: 0.84, blue: 0.89, alpha: 1),
contentTextColor: .black,
toolBackgroundColor: UIColor(red: 0.8, green: 0.84, blue: 0.89, alpha: 1),
toolControlTextColor: .black,
toolControlBorderUnselectColor: UIColor.lightGray.withAlphaComponent(0.5),
toolLineColor: UIColor.lightGray.withAlphaComponent(0.5)
)
} }
extension RDEPUBReaderTheme { extension RDEPUBReaderTheme {

View File

@ -5,6 +5,22 @@ import DTCoreText
#endif #endif
enum RDEPUBDarkImageAdjuster { enum RDEPUBDarkImageAdjuster {
private static let imageCache: NSCache<NSString, UIImage> = {
let cache = NSCache<NSString, UIImage>()
cache.countLimit = 100
cache.totalCostLimit = 52_428_800 // 50 MB
return cache
}()
private static func imageCost(of image: UIImage) -> Int {
let scale = image.scale
let width = Int(image.size.width * scale)
let height = Int(image.size.height * scale)
// 4 bytes per pixel (RGBA)
return width * height * 4
}
#if canImport(DTCoreText) #if canImport(DTCoreText)
static func adjustIfNeeded( static func adjustIfNeeded(
@ -12,13 +28,80 @@ enum RDEPUBDarkImageAdjuster {
in range: NSRange? = nil, in range: NSRange? = nil,
configuration: RDEPUBReaderConfiguration configuration: RDEPUBReaderConfiguration
) -> NSMutableAttributedString { ) -> NSMutableAttributedString {
// DTCoreText retains rendering state on its original image attachment. guard configuration.darkImageAdjustmentEnabled,
// Replacing that attachment with a newly rendered UIImage preserves its configuration.darkImageBlendRatio > 0,
// layout size but can leave its drawing callback empty in dark mode. configuration.theme.contentBackgroundColor.rd_isDarkBackground else {
// Keep the original attachment until image dimming can be performed return content
// without changing the attachment instance. }
let fullRange = NSRange(location: 0, length: content.length)
let targetRange = range.map { NSIntersectionRange($0, fullRange) } ?? fullRange
content.enumerateAttribute(.attachment, in: targetRange) { value, range, _ in
guard let attachment = value as? DTImageTextAttachment,
!isCoverAttachment(attachment),
let image = attachment.image,
shouldAdjust(image) else { return }
let adjustedAttachment = DTImageTextAttachment()
adjustedAttachment.image = adjustedImage(
image,
backgroundColor: configuration.theme.contentBackgroundColor,
blendRatio: configuration.darkImageBlendRatio,
cacheKey: cacheKey(for: attachment, image: image, configuration: configuration)
)
adjustedAttachment.originalSize = attachment.originalSize
adjustedAttachment.displaySize = attachment.displaySize
adjustedAttachment.verticalAlignment = attachment.verticalAlignment
adjustedAttachment.contentURL = attachment.contentURL
adjustedAttachment.hyperLinkURL = attachment.hyperLinkURL
adjustedAttachment.hyperLinkGUID = attachment.hyperLinkGUID
adjustedAttachment.attributes = attachment.attributes
content.addAttribute(.attachment, value: adjustedAttachment, range: range)
}
return content return content
} }
private static func isCoverAttachment(_ attachment: DTTextAttachment) -> Bool {
let lowercasedClasses = ((attachment.attributes["class"] as? String) ?? "").lowercased()
let lowercasedPath = attachment.contentURL?.lastPathComponent.lowercased()
?? ((attachment.attributes["src"] as? String) ?? "").lowercased()
return lowercasedClasses.contains("rd-front-cover-image") || lowercasedPath.contains("cover")
}
private static func shouldAdjust(_ image: UIImage) -> Bool {
image.size.width >= 80 && image.size.height >= 80
}
private static func cacheKey(
for attachment: DTImageTextAttachment,
image: UIImage,
configuration: RDEPUBReaderConfiguration
) -> NSString {
let source = attachment.contentURL?.absoluteString
?? "\(Unmanaged.passUnretained(image).toOpaque())"
return "\(source)|\(image.size.width)x\(image.size.height)|\(configuration.theme.contentBackgroundColor.rd_cssString)|\(configuration.darkImageBlendRatio)" as NSString
}
private static func adjustedImage(
_ image: UIImage,
backgroundColor: UIColor,
blendRatio: CGFloat,
cacheKey: NSString
) -> UIImage {
if let cached = imageCache.object(forKey: cacheKey) { return cached }
let format = UIGraphicsImageRendererFormat()
format.scale = image.scale
format.opaque = false
let renderer = UIGraphicsImageRenderer(size: image.size, format: format)
let adjusted = renderer.image { context in
image.draw(in: CGRect(origin: .zero, size: image.size))
backgroundColor.withAlphaComponent(max(0, min(0.35, blendRatio))).setFill()
context.cgContext.setBlendMode(.sourceAtop)
context.fill(CGRect(origin: .zero, size: image.size))
}
imageCache.setObject(adjusted, forKey: cacheKey, cost: imageCost(of: adjusted))
return adjusted
}
#endif #endif
} }

View File

@ -28,22 +28,4 @@ extension UIColor {
guard getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { return false } guard getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { return false }
return (0.2126 * red + 0.7152 * green + 0.0722 * blue) < 0.4 return (0.2126 * red + 0.7152 * green + 0.0722 * blue) < 0.4
} }
///
///
var rd_chromeShade: UIColor {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
guard getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { return self }
if rd_isDarkBackground {
let lift: CGFloat = 0.10
return UIColor(
red: red + (1 - red) * lift,
green: green + (1 - green) * lift,
blue: blue + (1 - blue) * lift,
alpha: alpha
)
}
let scale: CGFloat = 0.93
return UIColor(red: red * scale, green: green * scale, blue: blue * scale, alpha: alpha)
}
} }