fix(appearance): 完善深色模式和阅读页面对比度

- 区分按钮填充与图标颜色,统一书架、书源、搜索和阅读设置的选中态
- 调整编辑器语法高亮、键盘工具栏及统计卡片的浅深色显示
- 适配 RSS 正文和登录网页背景,保留书源自定义样式和页面状态
- 补充外观切换、页面对比度和管理页面的回归测试
This commit is contained in:
Aminark
2026-10-22 10:53:19 +08:00
parent bf72ebc894
commit 45cf5b5482
21 changed files with 611 additions and 46 deletions
@@ -47,7 +47,9 @@ struct ReadingStatisticsView: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(24)
.background(ReadingTheme.accent, in: RoundedRectangle(cornerRadius: 20))
.background(ReadingTheme.emphasisSurface, in: RoundedRectangle(cornerRadius: 20))
.accessibilityElement(children: .contain)
.accessibilityIdentifier("statistics.todayCard")
}
private var overviewGrid: some View {
+2 -2
View File
@@ -194,7 +194,7 @@ struct BookDetailView: View {
.font(.body.weight(.medium))
.frame(maxWidth: .infinity, minHeight: 28)
}
.buttonStyle(.borderedProminent)
.readingProminentButtonStyle()
.disabled(model.chapters.isEmpty)
Button {
if library.contains(model.book) {
@@ -210,7 +210,7 @@ struct BookDetailView: View {
.font(.subheadline)
.frame(maxWidth: dynamicTypeSize.isAccessibilitySize ? .infinity : nil, minHeight: 28)
}
.buttonStyle(.bordered)
.readingSecondaryButtonStyle()
}
@ViewBuilder
+1 -1
View File
@@ -155,7 +155,7 @@ struct ExploreView: View {
Text("添加带有发现分类的书源,即可在这里浏览书籍。")
} actions: {
NavigationLink("管理书源") { SourceManagementView() }
.buttonStyle(.borderedProminent)
.readingProminentButtonStyle()
}
} else {
LazyVStack(spacing: 0) {
+9 -4
View File
@@ -142,7 +142,7 @@ private struct RSSArticleListView: View {
ScrollView(.horizontal, showsIndicators: false) {
HStack { ForEach(Array(sorts.enumerated()), id: \.element.id) { index, sort in
Button(sort.name.isEmpty ? "默认" : sort.name) { selectedSort = index; Task { await refresh() } }
.buttonStyle(.bordered).tint(selectedSort == index ? ReadingTheme.accent : .secondary)
.readingChoiceButtonStyle(isSelected: selectedSort == index)
}}.padding(.horizontal)
}.padding(.vertical, 6)
}
@@ -435,7 +435,7 @@ private struct RSSDownloadView: View {
Image(systemName: "doc.badge.arrow.up").font(.system(size: 48)).foregroundStyle(ReadingTheme.accent)
Text(fileName).font(.headline)
Text(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .file)).foregroundStyle(.secondary)
Button("选择保存位置", systemImage: "folder") { showsExporter = true }.buttonStyle(.borderedProminent)
Button("选择保存位置", systemImage: "folder") { showsExporter = true }.readingProminentButtonStyle()
}
}.padding().navigationTitle("文件下载").toolbar { ToolbarItem(placement: .cancellationAction) { Button("关闭") { dismiss() } } }
.task(id: url) { await download() }
@@ -464,7 +464,7 @@ private struct RSSBinaryDocument: FileDocument {
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { .init(regularFileWithContents: data) }
}
private struct RSSPhotoView: View {
struct RSSPhotoView: View {
@Environment(\.dismiss) private var dismiss
let value: String
let source: RssSource
@@ -480,9 +480,13 @@ private struct RSSPhotoView: View {
else if loading { ProgressView() }
else { ReadingUnavailableView("图片加载失败", systemImage: "photo.badge.exclamationmark") }
}.frame(minWidth: proxy.size.width, minHeight: proxy.size.height)
}.background(.black)
}
.background(.black)
.environment(\.colorScheme, .dark)
.tint(.white)
}
.navigationTitle(source.sourceName).navigationBarTitleDisplayMode(.inline)
.readingNavigationBarBackground()
.toolbar {
ToolbarItem(placement: .primaryAction) { Button("保存到照片", systemImage: "square.and.arrow.down") { save() }.disabled(image == nil) }
ToolbarItem(placement: .confirmationAction) { Button("完成") { dismiss() } }
@@ -490,6 +494,7 @@ private struct RSSPhotoView: View {
.task(id: value) { await load() }
.alert("图片", isPresented: Binding(get: { message != nil }, set: { if !$0 { message = nil } })) { Button("好") { message = nil } } message: { Text(message ?? "") }
}
.readingTabBarHidden()
}
private func load() async {
+3
View File
@@ -41,6 +41,9 @@ struct RSSWebReaderView: UIViewRepresentable {
controller.addUserScript(.init(source: inject, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
}
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.isOpaque = false
webView.backgroundColor = UIColor(ReadingTheme.canvas)
webView.scrollView.backgroundColor = UIColor(ReadingTheme.canvas)
webView.navigationDelegate = context.coordinator
webView.uiDelegate = context.coordinator
webView.allowsBackForwardNavigationGestures = true
+16 -7
View File
@@ -492,7 +492,7 @@ struct ReaderView: View {
.accessibilityIdentifier("reader.failureChangeSource")
}
}
.buttonStyle(.bordered)
.readingSecondaryButtonStyle()
}
private func openSourceSwitch() {
@@ -1118,7 +1118,7 @@ struct ReaderView: View {
Image(systemName: "textformat.size.smaller")
.frame(width: 38, height: 32)
}
.buttonStyle(.bordered)
.readingSecondaryButtonStyle()
VStack(spacing: 2) {
Text("字号")
@@ -1135,7 +1135,7 @@ struct ReaderView: View {
Image(systemName: "textformat.size.larger")
.frame(width: 38, height: 32)
}
.buttonStyle(.bordered)
.readingSecondaryButtonStyle()
}
HStack(spacing: 8) {
@@ -1285,14 +1285,19 @@ struct ReaderView: View {
Text(choice.title)
.font(.caption2)
}
.foregroundStyle(pageAnimation == choice.value ? Color.white : Color.primary)
.foregroundStyle(pageAnimation == choice.value ? ReadingTheme.onAccent : Color.primary)
.frame(width: 58, height: 54)
.background(
pageAnimation == choice.value ? ReadingTheme.accent : Color.secondary.opacity(0.1),
pageAnimation == choice.value ? ReadingTheme.accentFill : Color.secondary.opacity(0.1),
in: RoundedRectangle(cornerRadius: 12, style: .continuous)
)
.overlay {
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(pageAnimation == choice.value ? ReadingTheme.selectionBorder : .clear, lineWidth: 1)
}
}
.buttonStyle(.plain)
.accessibilityAddTraits(pageAnimation == choice.value ? .isSelected : [])
}
}
}
@@ -1392,12 +1397,16 @@ struct ReaderView: View {
Button(action: action) {
Text(title)
.font(.caption.weight(.medium))
.foregroundStyle(selected ? Color.white : Color.primary)
.foregroundStyle(selected ? ReadingTheme.onAccent : Color.primary)
.padding(.horizontal, 12)
.padding(.vertical, 7)
.background(selected ? ReadingTheme.accent : Color.secondary.opacity(0.1), in: Capsule())
.background(selected ? ReadingTheme.accentFill : Color.secondary.opacity(0.1), in: Capsule())
.overlay {
Capsule().strokeBorder(selected ? ReadingTheme.selectionBorder : .clear, lineWidth: 1)
}
}
.buttonStyle(.plain)
.accessibilityAddTraits(selected ? .isSelected : [])
}
private func valueSlider(
@@ -39,7 +39,7 @@ struct QRCodeImportView: View {
} label: {
Label("从照片选择二维码", systemImage: "photo")
.frame(maxWidth: .infinity)
}.buttonStyle(.borderedProminent).disabled(isReading)
}.readingProminentButtonStyle().disabled(isReading)
Text("支持 HTTP(S) 链接、Legado 导入深链和 JSON 内容。识别后进入导入流程。")
.font(.footnote).foregroundStyle(.secondary)
if values.count > 1 {
@@ -3,6 +3,26 @@ import UIKit
struct SyntaxCodeEditor: UIViewRepresentable {
enum Language { case json, rule, javascript, regex }
// Syntax colors are text, so they need more contrast than system icon tints.
private enum Palette {
static let keyword = adaptive(0x7A2BA6, dark: 0xD5A5F5)
static let number = adaptive(0x895900, dark: 0xE6B66A)
static let operation = adaptive(0xAD285F, dark: 0xF49AB8)
static let punctuation = adaptive(0x14666E, dark: 0x83CED8)
static let string = adaptive(0x2D6B39, dark: 0x8CCF96)
static let key = adaptive(0x1E5EB2, dark: 0x93BFFF)
static let comment = adaptive(0x64646B, dark: 0xA4A4AE)
private static func adaptive(_ light: UInt32, dark: UInt32) -> UIColor {
UIColor { traits in
let hex = traits.userInterfaceStyle == .dark ? dark : light
return UIColor(red: CGFloat((hex >> 16) & 0xff) / 255,
green: CGFloat((hex >> 8) & 0xff) / 255,
blue: CGFloat(hex & 0xff) / 255, alpha: 1)
}
}
}
@Binding var text: String
var language: Language = .rule
@@ -13,6 +33,7 @@ struct SyntaxCodeEditor: UIViewRepresentable {
view.delegate = context.coordinator
view.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
view.backgroundColor = .secondarySystemGroupedBackground
view.tintColor = UIColor(ReadingTheme.accent)
view.textContainerInset = UIEdgeInsets(top: 14, left: 12, bottom: 20, right: 12)
view.autocorrectionType = .no
view.autocapitalizationType = .none
@@ -24,6 +45,7 @@ struct SyntaxCodeEditor: UIViewRepresentable {
view.accessibilityIdentifier = "syntaxCodeEditor"
if #available(iOS 16.0, *) { view.isFindInteractionEnabled = true }
let bar = UIToolbar()
bar.tintColor = UIColor(ReadingTheme.accent)
bar.sizeToFit()
bar.items = [
UIBarButtonItem(image: UIImage(systemName: "magnifyingglass"), primaryAction: UIAction(title: "", subtitle: nil) { [weak view, coordinator = context.coordinator] _ in
@@ -126,12 +148,12 @@ struct SyntaxCodeEditor: UIViewRepresentable {
storage.addAttributes([.font: UIFont.monospacedSystemFont(ofSize: 14, weight: .regular), .foregroundColor: UIColor.label], range: all)
if storage.length <= 500_000 {
let tokens: [(String, UIColor)] = [
(#"\b(?:true|false|null|undefined|return|var|let|const|if|else|function|new|for|while|try|catch|throw|await|async)\b"#, .systemPurple),
(#"\b\d+(?:\.\d+)?\b"#, .systemOrange),
(#"@(?:css|xpath|json|js|put|get):?|\$\.|&&|\|\||%%|##|\{\{|\}\}"#, .systemPink),
(#"[{}\[\]()]"#, .systemTeal),
(#""(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`"#, .systemGreen),
(#""(?:\\.|[^"\\])*"(?=\s*:)"#, .systemBlue),
(#"\b(?:true|false|null|undefined|return|var|let|const|if|else|function|new|for|while|try|catch|throw|await|async)\b"#, Palette.keyword),
(#"\b\d+(?:\.\d+)?\b"#, Palette.number),
(#"@(?:css|xpath|json|js|put|get):?|\$\.|&&|\|\||%%|##|\{\{|\}\}"#, Palette.operation),
(#"[{}\[\]()]"#, Palette.punctuation),
(#""(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`"#, Palette.string),
(#""(?:\\.|[^"\\])*"(?=\s*:)"#, Palette.key),
]
for (pattern, color) in tokens {
guard let regex = try? NSRegularExpression(pattern: pattern) else { continue }
@@ -143,7 +165,7 @@ struct SyntaxCodeEditor: UIViewRepresentable {
for pattern in [#"[.*+?^$|]"#, #"\\[dDsSwWbB0-9]|\{[0-9,]+\}"#] {
let regex = try? NSRegularExpression(pattern: pattern)
regex?.enumerateMatches(in: view.text, range: all) { result, _, _ in
if let result { storage.addAttribute(.foregroundColor, value: UIColor.systemPink, range: result.range) }
if let result { storage.addAttribute(.foregroundColor, value: Palette.operation, range: result.range) }
}
}
}
@@ -151,7 +173,7 @@ struct SyntaxCodeEditor: UIViewRepresentable {
let pattern = #"(?m)^\s*//.*$|/\*[\s\S]*?\*/"#
let regex = try? NSRegularExpression(pattern: pattern)
regex?.enumerateMatches(in: view.text, range: all) { result, _, _ in
if let result { storage.addAttribute(.foregroundColor, value: UIColor.secondaryLabel, range: result.range) }
if let result { storage.addAttribute(.foregroundColor, value: Palette.comment, range: result.range) }
}
}
}
+1 -2
View File
@@ -81,8 +81,7 @@ struct SourceDebugView: View {
Label(model.isRunning ? "正在调试…" : "运行完整链路", systemImage: "play.fill")
.font(.headline).frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.tint(ReadingTheme.accent)
.readingProminentButtonStyle()
.disabled(model.isRunning || model.keyword.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
}
+3 -4
View File
@@ -44,11 +44,10 @@ struct SourceEditorView: View {
HStack {
ForEach(BookSourceEditorSchema.sections) { section in
Button(section.title) { sectionID = section.id }
.buttonStyle(.bordered)
.tint(sectionID == section.id ? ReadingTheme.accent : .secondary)
.readingChoiceButtonStyle(isSelected: sectionID == section.id)
}
Button("选项") { sectionID = "options" }.buttonStyle(.bordered)
.tint(sectionID == "options" ? ReadingTheme.accent : .secondary)
Button("选项") { sectionID = "options" }
.readingChoiceButtonStyle(isSelected: sectionID == "options")
}.padding(.horizontal)
}.padding(.vertical, 6)
Form {
@@ -1,5 +1,6 @@
@preconcurrency import WebKit
import SwiftUI
import UIKit
struct SourceLoginView: View {
let source: BookSource
@@ -74,6 +75,9 @@ private struct SourceLoginWebView: UIViewRepresentable {
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = .default()
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.isOpaque = false
webView.backgroundColor = UIColor(ReadingTheme.canvas)
webView.scrollView.backgroundColor = UIColor(ReadingTheme.canvas)
webView.navigationDelegate = context.coordinator
context.coordinator.installExistingCookies(in: webView) { webView.load(request) }
return webView
@@ -61,7 +61,7 @@ struct SourceManagementView: View {
Text("新建书源,或从文件、链接、JSON、二维码导入。")
} actions: {
Button("导入书源", action: presentFileImporter)
.buttonStyle(.borderedProminent)
.readingProminentButtonStyle()
}
} else {
List {