Compare commits
No commits in common. "72c1f8d89c34745e7e86788605c58fc60af67e25" and "8ccb7157b28a95097f148724b167e62a48208e6a" have entirely different histories.
72c1f8d89c
...
8ccb7157b2
@ -7,8 +7,8 @@
|
|||||||
## 语言与工程约束
|
## 语言与工程约束
|
||||||
|
|
||||||
- **主要语言**:Swift(Podspec 声明 `s.swift_versions = ["5.10"]`,见 `RDEpubReaderView.podspec`)
|
- **主要语言**:Swift(Podspec 声明 `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`
|
||||||
|
|||||||
@ -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
30
Podfile
Normal 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
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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, "设置按钮未出现")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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; }
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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?()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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 }
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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),
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user