feat: improve epub reader controls and annotations
This commit is contained in:
+215
-248
@@ -1,30 +1,19 @@
|
||||
import UIKit
|
||||
|
||||
/// Compact reader settings drawer. It deliberately keeps the book visible and
|
||||
/// exposes only the controls currently supported by the reader configuration.
|
||||
final class RDEPUBReaderSettingsViewController: UIViewController {
|
||||
|
||||
var onBrightnessChange: ((CGFloat) -> Void)?
|
||||
|
||||
var onFontSizeChange: ((CGFloat) -> Void)?
|
||||
|
||||
var onFontChoiceChange: ((RDEPUBReaderFontChoice) -> Void)?
|
||||
|
||||
var onLineHeightChange: ((CGFloat) -> Void)?
|
||||
|
||||
var onColumnCountChange: ((Int) -> Void)?
|
||||
|
||||
var onDisplayTypeChange: ((RDEpubReaderView.DisplayType) -> Void)?
|
||||
|
||||
var onThemeChange: ((RDEPUBReaderTheme) -> Void)?
|
||||
|
||||
var onDismiss: (() -> Void)?
|
||||
|
||||
private enum ThemePreset: Int, CaseIterable {
|
||||
case light
|
||||
case yellow
|
||||
case green
|
||||
case pink
|
||||
case blue
|
||||
case dark
|
||||
case light, yellow, green, pink, blue, dark
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
@@ -49,50 +38,24 @@ final class RDEPUBReaderSettingsViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private let scrollView = UIScrollView()
|
||||
|
||||
private let contentStack: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 20
|
||||
return stackView
|
||||
}()
|
||||
|
||||
private let handleView = UIView()
|
||||
private let closeButton = UIButton(type: .system)
|
||||
private let brightnessSlider = UISlider()
|
||||
|
||||
private let fontValueLabel = UILabel()
|
||||
|
||||
private let decreaseFontButton = UIButton(type: .system)
|
||||
|
||||
private let increaseFontButton = UIButton(type: .system)
|
||||
|
||||
private let fontChoiceControl = UISegmentedControl(items: RDEPUBReaderFontChoice.allCases.map(\.displayName))
|
||||
|
||||
private let lineHeightControl = UISegmentedControl(items: ["紧凑", "标准", "宽松"])
|
||||
|
||||
private let columnCountControl = UISegmentedControl(items: ["单栏", "双栏"])
|
||||
|
||||
private let displayTypeControl = UISegmentedControl(items: ["仿真", "横滑", "竖滑"])
|
||||
|
||||
private let themeStackView: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .horizontal
|
||||
stackView.distribution = .fillEqually
|
||||
stackView.spacing = 12
|
||||
return stackView
|
||||
}()
|
||||
|
||||
private let fontSizeSlider = UISlider()
|
||||
private let fontSizeLabel = UILabel()
|
||||
private let fontChoiceButton = UIButton(type: .system)
|
||||
private let lineHeightButton = UIButton(type: .system)
|
||||
private let displayTypeButton = UIButton(type: .system)
|
||||
private let themeStack = UIStackView()
|
||||
private var themeButtons: [UIButton] = []
|
||||
|
||||
private let lineHeightValues: [CGFloat] = [1.3, 1.6, 1.9]
|
||||
|
||||
private var currentConfiguration: RDEPUBReaderConfiguration
|
||||
private let lineHeightValues: [CGFloat] = [1.3, 1.6, 1.9]
|
||||
private var hasNotifiedDismissal = false
|
||||
|
||||
init(configuration: RDEPUBReaderConfiguration, brightness: CGFloat) {
|
||||
self.currentConfiguration = configuration
|
||||
currentConfiguration = configuration
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
brightnessSlider.value = Float(brightness)
|
||||
title = "样式设置"
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -101,262 +64,266 @@ final class RDEPUBReaderSettingsViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupNavigationItems()
|
||||
setupViews()
|
||||
syncControls()
|
||||
applyTheme(currentConfiguration.theme)
|
||||
}
|
||||
|
||||
private func setupNavigationItems() {
|
||||
let doneItem = UIBarButtonItem(title: "完成", style: .done, target: self, action: #selector(doneAction))
|
||||
doneItem.accessibilityIdentifier = "epub.reader.settings.done"
|
||||
navigationItem.rightBarButtonItem = doneItem
|
||||
}
|
||||
|
||||
private func setupViews() {
|
||||
view.addSubview(scrollView)
|
||||
scrollView.accessibilityIdentifier = "epub.reader.settings.scroll"
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.addSubview(contentStack)
|
||||
contentStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.accessibilityIdentifier = "epub.reader.settings.panel"
|
||||
view.layer.cornerRadius = 24
|
||||
view.layer.cornerCurve = .continuous
|
||||
view.clipsToBounds = true
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
||||
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
[handleView, closeButton, brightnessSlider, fontSizeSlider, fontSizeLabel,
|
||||
fontChoiceButton, lineHeightButton, displayTypeButton, themeStack].forEach {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview($0)
|
||||
}
|
||||
|
||||
contentStack.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16),
|
||||
contentStack.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16),
|
||||
contentStack.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 20),
|
||||
contentStack.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -24),
|
||||
contentStack.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -32)
|
||||
])
|
||||
handleView.layer.cornerRadius = 3
|
||||
closeButton.setImage(UIImage(systemName: "xmark"), for: .normal)
|
||||
closeButton.accessibilityIdentifier = "epub.reader.settings.done"
|
||||
closeButton.accessibilityLabel = "完成"
|
||||
closeButton.addTarget(self, action: #selector(doneAction), for: .touchUpInside)
|
||||
|
||||
brightnessSlider.minimumValue = 0
|
||||
brightnessSlider.maximumValue = 1
|
||||
brightnessSlider.accessibilityIdentifier = "epub.reader.settings.brightness"
|
||||
brightnessSlider.addTarget(self, action: #selector(brightnessChanged(_:)), for: .valueChanged)
|
||||
|
||||
fontValueLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 16, weight: .semibold)
|
||||
fontValueLabel.textAlignment = .center
|
||||
fontValueLabel.accessibilityIdentifier = "epub.reader.settings.font.value"
|
||||
fontValueLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||
fontSizeSlider.minimumValue = 12
|
||||
fontSizeSlider.maximumValue = 36
|
||||
fontSizeSlider.accessibilityIdentifier = "epub.reader.settings.font.size"
|
||||
fontSizeSlider.addTarget(self, action: #selector(fontSizeChanged(_:)), for: .valueChanged)
|
||||
fontSizeLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 14, weight: .semibold)
|
||||
fontSizeLabel.textAlignment = .center
|
||||
fontSizeLabel.accessibilityIdentifier = "epub.reader.settings.font.value"
|
||||
|
||||
configureFontButton(decreaseFontButton, title: "A-")
|
||||
configureFontButton(increaseFontButton, title: "A+")
|
||||
decreaseFontButton.accessibilityIdentifier = "epub.reader.settings.font.decrease"
|
||||
increaseFontButton.accessibilityIdentifier = "epub.reader.settings.font.increase"
|
||||
decreaseFontButton.addTarget(self, action: #selector(decreaseFontAction), for: .touchUpInside)
|
||||
increaseFontButton.addTarget(self, action: #selector(increaseFontAction), for: .touchUpInside)
|
||||
|
||||
lineHeightControl.accessibilityIdentifier = "epub.reader.settings.lineHeight"
|
||||
fontChoiceControl.accessibilityIdentifier = "epub.reader.settings.font.choice"
|
||||
columnCountControl.accessibilityIdentifier = "epub.reader.settings.columns"
|
||||
displayTypeControl.accessibilityIdentifier = "epub.reader.settings.displayType"
|
||||
fontChoiceControl.addTarget(self, action: #selector(fontChoiceChanged(_:)), for: .valueChanged)
|
||||
lineHeightControl.addTarget(self, action: #selector(lineHeightChanged(_:)), for: .valueChanged)
|
||||
columnCountControl.addTarget(self, action: #selector(columnCountChanged(_:)), for: .valueChanged)
|
||||
displayTypeControl.addTarget(self, action: #selector(displayTypeChanged(_:)), for: .valueChanged)
|
||||
configurePill(fontChoiceButton, title: "字体")
|
||||
configurePill(lineHeightButton, title: "行距")
|
||||
configurePill(displayTypeButton, title: "翻页方式")
|
||||
fontChoiceButton.accessibilityIdentifier = "epub.reader.settings.font.choice"
|
||||
lineHeightButton.accessibilityIdentifier = "epub.reader.settings.lineHeight"
|
||||
displayTypeButton.accessibilityIdentifier = "epub.reader.settings.displayType"
|
||||
fontChoiceButton.addTarget(self, action: #selector(fontChoiceAction), for: .touchUpInside)
|
||||
lineHeightButton.addTarget(self, action: #selector(lineHeightAction), for: .touchUpInside)
|
||||
displayTypeButton.addTarget(self, action: #selector(displayTypeAction), for: .touchUpInside)
|
||||
|
||||
themeStack.axis = .horizontal
|
||||
themeStack.distribution = .fillEqually
|
||||
themeStack.spacing = 14
|
||||
ThemePreset.allCases.forEach { preset in
|
||||
let button = UIButton(type: .system)
|
||||
button.tag = preset.rawValue
|
||||
button.layer.cornerRadius = 18
|
||||
button.layer.borderWidth = 1.5
|
||||
button.backgroundColor = preset.theme.contentBackgroundColor
|
||||
button.layer.cornerRadius = 15
|
||||
button.layer.borderWidth = 1
|
||||
button.accessibilityLabel = preset.title
|
||||
button.accessibilityIdentifier = "epub.reader.settings.theme.\(preset.rawValue)"
|
||||
button.addTarget(self, action: #selector(themeButtonAction(_:)), for: .touchUpInside)
|
||||
themeButtons.append(button)
|
||||
themeStackView.addArrangedSubview(button)
|
||||
NSLayoutConstraint.activate([
|
||||
button.heightAnchor.constraint(equalToConstant: 36)
|
||||
])
|
||||
themeStack.addArrangedSubview(button)
|
||||
button.heightAnchor.constraint(equalToConstant: 30).isActive = true
|
||||
}
|
||||
|
||||
contentStack.addArrangedSubview(makeSection(title: "亮度", content: brightnessSlider))
|
||||
contentStack.addArrangedSubview(makeSection(title: "字号", content: makeFontSizeRow()))
|
||||
contentStack.addArrangedSubview(makeSection(title: "字体", content: fontChoiceControl))
|
||||
contentStack.addArrangedSubview(makeSection(title: "行距", content: lineHeightControl))
|
||||
contentStack.addArrangedSubview(makeSection(title: "分栏", content: columnCountControl))
|
||||
contentStack.addArrangedSubview(makeSection(title: "翻页方式", content: displayTypeControl))
|
||||
contentStack.addArrangedSubview(makeSection(title: "主题", content: themeStackView))
|
||||
}
|
||||
let brightnessLabel = makeLabel("亮度")
|
||||
let fontSizeTitle = makeLabel("字体大小")
|
||||
let smallA = makeLabel("A", font: 18)
|
||||
let largeA = makeLabel("A", font: 25)
|
||||
[brightnessLabel, fontSizeTitle, smallA, largeA].forEach(view.addSubview)
|
||||
|
||||
private func makeSection(title: String, content: UIView) -> UIView {
|
||||
let container = UIStackView()
|
||||
container.axis = .vertical
|
||||
container.spacing = 10
|
||||
let selectorStack = UIStackView(arrangedSubviews: [fontChoiceButton, lineHeightButton, displayTypeButton])
|
||||
selectorStack.axis = .horizontal
|
||||
selectorStack.distribution = .fillEqually
|
||||
selectorStack.spacing = 10
|
||||
selectorStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(selectorStack)
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = title
|
||||
titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .semibold)
|
||||
|
||||
content.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addArrangedSubview(titleLabel)
|
||||
container.addArrangedSubview(content)
|
||||
return container
|
||||
}
|
||||
|
||||
private func makeFontSizeRow() -> UIView {
|
||||
let stackView = UIStackView(arrangedSubviews: [decreaseFontButton, fontValueLabel, increaseFontButton])
|
||||
stackView.axis = .horizontal
|
||||
stackView.alignment = .center
|
||||
stackView.distribution = .fill
|
||||
stackView.spacing = 12
|
||||
let dividerOne = makeDivider()
|
||||
let dividerTwo = makeDivider()
|
||||
[dividerOne, dividerTwo].forEach(view.addSubview)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
decreaseFontButton.widthAnchor.constraint(equalToConstant: 64),
|
||||
increaseFontButton.widthAnchor.constraint(equalToConstant: 64),
|
||||
decreaseFontButton.heightAnchor.constraint(equalToConstant: 36),
|
||||
increaseFontButton.heightAnchor.constraint(equalToConstant: 36)
|
||||
handleView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
|
||||
handleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
handleView.widthAnchor.constraint(equalToConstant: 42),
|
||||
handleView.heightAnchor.constraint(equalToConstant: 5),
|
||||
closeButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
||||
closeButton.centerYAnchor.constraint(equalTo: handleView.centerYAnchor),
|
||||
closeButton.widthAnchor.constraint(equalToConstant: 32),
|
||||
closeButton.heightAnchor.constraint(equalToConstant: 32),
|
||||
|
||||
brightnessLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||
brightnessLabel.topAnchor.constraint(equalTo: handleView.bottomAnchor, constant: 14),
|
||||
brightnessSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||
brightnessSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
|
||||
brightnessSlider.topAnchor.constraint(equalTo: brightnessLabel.bottomAnchor, constant: 5),
|
||||
dividerOne.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||
dividerOne.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
|
||||
dividerOne.topAnchor.constraint(equalTo: brightnessSlider.bottomAnchor, constant: 10),
|
||||
|
||||
fontSizeTitle.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||
fontSizeTitle.topAnchor.constraint(equalTo: dividerOne.bottomAnchor, constant: 10),
|
||||
smallA.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||
smallA.centerYAnchor.constraint(equalTo: fontSizeSlider.centerYAnchor),
|
||||
fontSizeSlider.leadingAnchor.constraint(equalTo: smallA.trailingAnchor, constant: 10),
|
||||
fontSizeSlider.trailingAnchor.constraint(equalTo: largeA.leadingAnchor, constant: -10),
|
||||
fontSizeSlider.topAnchor.constraint(equalTo: fontSizeTitle.bottomAnchor, constant: 4),
|
||||
largeA.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
|
||||
largeA.centerYAnchor.constraint(equalTo: fontSizeSlider.centerYAnchor),
|
||||
fontSizeLabel.centerXAnchor.constraint(equalTo: fontSizeSlider.centerXAnchor),
|
||||
fontSizeLabel.bottomAnchor.constraint(equalTo: fontSizeSlider.topAnchor, constant: -1),
|
||||
dividerTwo.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||
dividerTwo.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
|
||||
dividerTwo.topAnchor.constraint(equalTo: fontSizeSlider.bottomAnchor, constant: 10),
|
||||
|
||||
selectorStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||
selectorStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
|
||||
selectorStack.topAnchor.constraint(equalTo: dividerTwo.bottomAnchor, constant: 12),
|
||||
selectorStack.heightAnchor.constraint(equalToConstant: 42),
|
||||
themeStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
|
||||
themeStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
|
||||
themeStack.topAnchor.constraint(equalTo: selectorStack.bottomAnchor, constant: 16),
|
||||
themeStack.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -14)
|
||||
])
|
||||
return stackView
|
||||
}
|
||||
|
||||
private func configureFontButton(_ button: UIButton, title: String) {
|
||||
private func makeLabel(_ text: String, font: CGFloat = 13) -> UILabel {
|
||||
let label = UILabel()
|
||||
label.text = text
|
||||
label.font = UIFont.systemFont(ofSize: font, weight: font > 16 ? .regular : .semibold)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
return label
|
||||
}
|
||||
|
||||
private func makeDivider() -> UIView {
|
||||
let divider = UIView()
|
||||
divider.translatesAutoresizingMaskIntoConstraints = false
|
||||
divider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale).isActive = true
|
||||
return divider
|
||||
}
|
||||
|
||||
private func configurePill(_ button: UIButton, title: String) {
|
||||
button.setTitle(title, for: .normal)
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
|
||||
button.layer.cornerRadius = 18
|
||||
button.layer.borderWidth = 1
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
|
||||
button.layer.cornerRadius = 13
|
||||
}
|
||||
|
||||
private func syncControls() {
|
||||
fontValueLabel.text = String(Int(currentConfiguration.fontSize.rounded()))
|
||||
fontChoiceControl.selectedSegmentIndex = RDEPUBReaderFontChoice.allCases.firstIndex(of: currentConfiguration.fontChoice) ?? 0
|
||||
|
||||
let lineHeightIndex = lineHeightValues.enumerated().min { abs($0.element - currentConfiguration.lineHeightMultiple) < abs($1.element - currentConfiguration.lineHeightMultiple) }?.offset ?? 1
|
||||
lineHeightControl.selectedSegmentIndex = lineHeightIndex
|
||||
columnCountControl.selectedSegmentIndex = currentConfiguration.numberOfColumns > 1 ? 1 : 0
|
||||
fontSizeSlider.value = Float(currentConfiguration.fontSize)
|
||||
fontSizeLabel.text = String(Int(currentConfiguration.fontSize.rounded()))
|
||||
updateSelectorTitles()
|
||||
updateThemeSelection(ThemePreset.allCases.first(where: { $0.theme == currentConfiguration.theme }) ?? .light)
|
||||
}
|
||||
|
||||
private func updateSelectorTitles() {
|
||||
fontChoiceButton.setTitle("\(currentConfiguration.fontChoice.displayName) ›", for: .normal)
|
||||
let lineIndex = lineHeightValues.enumerated().min {
|
||||
abs($0.element - currentConfiguration.lineHeightMultiple) < abs($1.element - currentConfiguration.lineHeightMultiple)
|
||||
}?.offset ?? 1
|
||||
fontChoiceButton.accessibilityValue = currentConfiguration.fontChoice.displayName
|
||||
let lineTitles = ["紧凑", "标准", "宽松"]
|
||||
lineHeightButton.setTitle("\(lineTitles[lineIndex]) ›", for: .normal)
|
||||
lineHeightButton.accessibilityValue = lineTitles[lineIndex]
|
||||
let displayTitle: String
|
||||
switch currentConfiguration.displayType {
|
||||
case .pageCurl:
|
||||
displayTypeControl.selectedSegmentIndex = 0
|
||||
case .horizontalScroll:
|
||||
displayTypeControl.selectedSegmentIndex = 1
|
||||
case .verticalScroll:
|
||||
displayTypeControl.selectedSegmentIndex = 2
|
||||
case .pageCurl: displayTitle = "仿真"
|
||||
case .horizontalScroll: displayTitle = "横滑"
|
||||
case .verticalScroll: displayTitle = "竖滑"
|
||||
}
|
||||
|
||||
let selectedPreset = ThemePreset.allCases.first(where: { $0.theme == currentConfiguration.theme }) ?? .light
|
||||
updateThemeSelection(selectedPreset)
|
||||
updateControlAccessibilityValues()
|
||||
displayTypeButton.setTitle("\(displayTitle) ›", for: .normal)
|
||||
displayTypeButton.accessibilityValue = displayTitle
|
||||
}
|
||||
|
||||
private func applyTheme(_ theme: RDEPUBReaderTheme) {
|
||||
view.backgroundColor = theme.contentBackgroundColor
|
||||
scrollView.backgroundColor = theme.contentBackgroundColor
|
||||
contentStack.arrangedSubviews
|
||||
.compactMap { $0 as? UIStackView }
|
||||
.flatMap { $0.arrangedSubviews }
|
||||
.compactMap { $0 as? UILabel }
|
||||
.forEach { $0.textColor = theme.contentTextColor }
|
||||
|
||||
fontValueLabel.textColor = theme.contentTextColor
|
||||
[decreaseFontButton, increaseFontButton].forEach { button in
|
||||
button.setTitleColor(theme.toolControlTextColor, for: .normal)
|
||||
button.layer.borderColor = theme.toolControlBorderUnselectColor.cgColor
|
||||
button.backgroundColor = theme.toolBackgroundColor
|
||||
view.backgroundColor = theme.toolBackgroundColor.withAlphaComponent(0.98)
|
||||
let textColor = theme.toolControlTextColor
|
||||
handleView.backgroundColor = textColor.withAlphaComponent(0.45)
|
||||
closeButton.tintColor = textColor
|
||||
view.subviews.compactMap { $0 as? UILabel }.forEach { $0.textColor = textColor }
|
||||
view.subviews.filter { $0 !== handleView && $0 !== closeButton && !($0 is UISlider) && !($0 is UIStackView) && !($0 is UILabel) }
|
||||
.forEach { $0.backgroundColor = textColor.withAlphaComponent(0.12) }
|
||||
[fontChoiceButton, lineHeightButton, displayTypeButton].forEach {
|
||||
$0.setTitleColor(textColor, for: .normal)
|
||||
$0.backgroundColor = textColor.withAlphaComponent(0.10)
|
||||
}
|
||||
|
||||
[fontChoiceControl, lineHeightControl, columnCountControl, displayTypeControl].forEach { control in
|
||||
control.backgroundColor = theme.toolBackgroundColor
|
||||
if #available(iOS 13.0, *) {
|
||||
control.selectedSegmentTintColor = theme.toolControlTextColor.withAlphaComponent(0.14)
|
||||
} else {
|
||||
control.tintColor = theme.toolControlTextColor
|
||||
}
|
||||
control.setTitleTextAttributes([.foregroundColor: theme.contentTextColor], for: .normal)
|
||||
control.setTitleTextAttributes([.foregroundColor: theme.toolControlTextColor], for: .selected)
|
||||
}
|
||||
|
||||
navigationController?.navigationBar.tintColor = theme.toolControlTextColor
|
||||
navigationController?.navigationBar.barTintColor = theme.toolBackgroundColor
|
||||
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor]
|
||||
brightnessSlider.minimumTrackTintColor = .systemBlue
|
||||
fontSizeSlider.minimumTrackTintColor = .systemBlue
|
||||
brightnessSlider.maximumTrackTintColor = textColor.withAlphaComponent(0.22)
|
||||
fontSizeSlider.maximumTrackTintColor = textColor.withAlphaComponent(0.22)
|
||||
updateThemeSelection(ThemePreset.allCases.first(where: { $0.theme == theme }) ?? .light)
|
||||
}
|
||||
|
||||
private func updateThemeSelection(_ preset: ThemePreset) {
|
||||
themeButtons.forEach { button in
|
||||
let isSelected = button.tag == preset.rawValue
|
||||
button.layer.borderWidth = isSelected ? 2 : 1
|
||||
button.layer.borderColor = isSelected ? currentConfiguration.theme.toolControlTextColor.cgColor : currentConfiguration.theme.toolControlBorderUnselectColor.cgColor
|
||||
button.accessibilityValue = isSelected ? "selected" : "unselected"
|
||||
let selected = button.tag == preset.rawValue
|
||||
button.layer.borderWidth = selected ? 2.5 : 1
|
||||
button.layer.borderColor = selected ? UIColor.systemBlue.cgColor : UIColor.white.withAlphaComponent(0.28).cgColor
|
||||
button.accessibilityValue = selected ? "selected" : "unselected"
|
||||
}
|
||||
}
|
||||
|
||||
private func updateControlAccessibilityValues() {
|
||||
fontChoiceControl.accessibilityValue = fontChoiceControl.titleForSegment(at: fontChoiceControl.selectedSegmentIndex)
|
||||
lineHeightControl.accessibilityValue = lineHeightControl.titleForSegment(at: lineHeightControl.selectedSegmentIndex)
|
||||
columnCountControl.accessibilityValue = columnCountControl.titleForSegment(at: columnCountControl.selectedSegmentIndex)
|
||||
displayTypeControl.accessibilityValue = displayTypeControl.titleForSegment(at: displayTypeControl.selectedSegmentIndex)
|
||||
func notifyDismissalIfNeeded() {
|
||||
guard !hasNotifiedDismissal else { return }
|
||||
hasNotifiedDismissal = true
|
||||
onDismiss?()
|
||||
}
|
||||
|
||||
@objc private func doneAction() {
|
||||
dismiss(animated: true) { [weak self] in
|
||||
self?.onDismiss?()
|
||||
notifyDismissalIfNeeded()
|
||||
dismiss(animated: true)
|
||||
}
|
||||
@objc private func brightnessChanged(_ sender: UISlider) { onBrightnessChange?(CGFloat(sender.value)) }
|
||||
|
||||
@objc private func fontSizeChanged(_ sender: UISlider) {
|
||||
let value = CGFloat(sender.value.rounded())
|
||||
guard value != currentConfiguration.fontSize else { return }
|
||||
currentConfiguration.fontSize = value
|
||||
fontSizeLabel.text = String(Int(value))
|
||||
onFontSizeChange?(value)
|
||||
}
|
||||
|
||||
@objc private func fontChoiceAction() {
|
||||
let alert = UIAlertController(title: "字体", message: nil, preferredStyle: .actionSheet)
|
||||
RDEPUBReaderFontChoice.allCases.forEach { choice in
|
||||
alert.addAction(UIAlertAction(title: choice.displayName, style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.currentConfiguration.fontChoice = choice
|
||||
self.updateSelectorTitles()
|
||||
self.onFontChoiceChange?(choice)
|
||||
})
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc private func brightnessChanged(_ slider: UISlider) {
|
||||
onBrightnessChange?(CGFloat(slider.value))
|
||||
}
|
||||
|
||||
@objc private func decreaseFontAction() {
|
||||
let nextValue = max(12, currentConfiguration.fontSize - 1)
|
||||
guard nextValue != currentConfiguration.fontSize else { return }
|
||||
currentConfiguration.fontSize = nextValue
|
||||
fontValueLabel.text = String(Int(nextValue.rounded()))
|
||||
onFontSizeChange?(nextValue)
|
||||
}
|
||||
|
||||
@objc private func increaseFontAction() {
|
||||
let nextValue = min(36, currentConfiguration.fontSize + 1)
|
||||
guard nextValue != currentConfiguration.fontSize else { return }
|
||||
currentConfiguration.fontSize = nextValue
|
||||
fontValueLabel.text = String(Int(nextValue.rounded()))
|
||||
onFontSizeChange?(nextValue)
|
||||
}
|
||||
|
||||
@objc private func fontChoiceChanged(_ control: UISegmentedControl) {
|
||||
let choices = RDEPUBReaderFontChoice.allCases
|
||||
let index = max(0, min(control.selectedSegmentIndex, choices.count - 1))
|
||||
let choice = choices[index]
|
||||
guard choice != currentConfiguration.fontChoice else { return }
|
||||
currentConfiguration.fontChoice = choice
|
||||
updateControlAccessibilityValues()
|
||||
onFontChoiceChange?(choice)
|
||||
}
|
||||
|
||||
@objc private func lineHeightChanged(_ control: UISegmentedControl) {
|
||||
let index = max(0, min(control.selectedSegmentIndex, lineHeightValues.count - 1))
|
||||
let value = lineHeightValues[index]
|
||||
currentConfiguration.lineHeightMultiple = value
|
||||
updateControlAccessibilityValues()
|
||||
onLineHeightChange?(value)
|
||||
}
|
||||
|
||||
@objc private func columnCountChanged(_ control: UISegmentedControl) {
|
||||
let numberOfColumns = control.selectedSegmentIndex == 1 ? 2 : 1
|
||||
currentConfiguration.numberOfColumns = numberOfColumns
|
||||
updateControlAccessibilityValues()
|
||||
onColumnCountChange?(numberOfColumns)
|
||||
}
|
||||
|
||||
@objc private func displayTypeChanged(_ control: UISegmentedControl) {
|
||||
let displayType: RDEpubReaderView.DisplayType
|
||||
switch control.selectedSegmentIndex {
|
||||
case 1:
|
||||
displayType = .horizontalScroll
|
||||
case 2:
|
||||
displayType = .verticalScroll
|
||||
default:
|
||||
displayType = .pageCurl
|
||||
@objc private func lineHeightAction() {
|
||||
let alert = UIAlertController(title: "行距", message: nil, preferredStyle: .actionSheet)
|
||||
zip(["紧凑", "标准", "宽松"], lineHeightValues).forEach { title, value in
|
||||
alert.addAction(UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.currentConfiguration.lineHeightMultiple = value
|
||||
self.updateSelectorTitles()
|
||||
self.onLineHeightChange?(value)
|
||||
})
|
||||
}
|
||||
currentConfiguration.displayType = displayType
|
||||
updateControlAccessibilityValues()
|
||||
onDisplayTypeChange?(displayType)
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc private func displayTypeAction() {
|
||||
let alert = UIAlertController(title: "翻页方式", message: nil, preferredStyle: .actionSheet)
|
||||
[("仿真", RDEpubReaderView.DisplayType.pageCurl), ("横滑", .horizontalScroll), ("竖滑", .verticalScroll)].forEach { title, type in
|
||||
alert.addAction(UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.currentConfiguration.displayType = type
|
||||
self.updateSelectorTitles()
|
||||
self.onDisplayTypeChange?(type)
|
||||
})
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
@objc private func themeButtonAction(_ sender: UIButton) {
|
||||
|
||||
Reference in New Issue
Block a user