mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
Add separate light/dark mode accent colors
Adds an option in Appearance settings to use different accent colors in light and dark mode, with a toggle to keep a single shared color (the default, preserving existing behavior). - New synced settings keys: accentColorDark, customAccentColorDark, useSeparateDarkAccentColor; dark values fall back to the light selection until explicitly set, so no migration is needed - resolvedAccentColor returns a dynamic platform color (UIColor trait provider / NSColor appearance provider) when the toggle is on, so the root tint and all direct readers adapt to light/dark automatically without consumer changes - Accent Color section shows two stacked preset+custom grids with Light/Dark headers when enabled, extracted into AccentColorGrid; CustomAccentColorButton now takes bindings so the macOS color panel edits whichever target was last opened - New DarkAccentColorTests covering sync contract, default-off state, fallbacks, and toggle-off resolution Claude-Session: https://claude.ai/code/session_0154KH8RAVAvm6iVanhmoW8o
This commit is contained in:
@@ -14,6 +14,9 @@ enum SettingsKey: String, CaseIterable {
|
||||
case theme
|
||||
case accentColor
|
||||
case customAccentColor
|
||||
case accentColorDark
|
||||
case customAccentColorDark
|
||||
case useSeparateDarkAccentColor
|
||||
case showWatchedCheckmark
|
||||
|
||||
// Playback
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
#if os(iOS)
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#elseif os(macOS)
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
@@ -50,11 +50,78 @@ extension SettingsManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// The effective accent color, resolving `.custom` to the user-picked value.
|
||||
var resolvedAccentColor: Color {
|
||||
/// Whether the user picked a separate accent color for dark mode.
|
||||
var useSeparateDarkAccentColor: Bool {
|
||||
get {
|
||||
if let cached = _useSeparateDarkAccentColor { return cached }
|
||||
return bool(for: .useSeparateDarkAccentColor, default: false)
|
||||
}
|
||||
set {
|
||||
_useSeparateDarkAccentColor = newValue
|
||||
set(newValue, for: .useSeparateDarkAccentColor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Accent color choice for dark mode. Falls back to the light selection until explicitly set.
|
||||
var accentColorDark: AccentColor {
|
||||
get {
|
||||
if let cached = _accentColorDark { return cached }
|
||||
if let raw = string(for: .accentColorDark), let value = AccentColor(rawValue: raw) {
|
||||
return value
|
||||
}
|
||||
return accentColor
|
||||
}
|
||||
set {
|
||||
_accentColorDark = newValue
|
||||
set(newValue.rawValue, for: .accentColorDark)
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom accent color for dark mode. Falls back to the light custom color until explicitly set.
|
||||
var customAccentColorDark: Color {
|
||||
get {
|
||||
if let hex = _customAccentColorDark ?? string(for: .customAccentColorDark),
|
||||
let color = Color(hex: hex) {
|
||||
return color
|
||||
}
|
||||
return customAccentColor
|
||||
}
|
||||
set {
|
||||
let hex = newValue.toHexString()
|
||||
_customAccentColorDark = hex
|
||||
set(hex, for: .customAccentColorDark)
|
||||
}
|
||||
}
|
||||
|
||||
/// Light (or shared) accent color, resolving `.custom` to the user-picked value.
|
||||
private var resolvedLightAccentColor: Color {
|
||||
accentColor == .custom ? customAccentColor : accentColor.color
|
||||
}
|
||||
|
||||
/// Dark accent color, resolving `.custom` to the user-picked value.
|
||||
private var resolvedDarkAccentColor: Color {
|
||||
accentColorDark == .custom ? customAccentColorDark : accentColorDark.color
|
||||
}
|
||||
|
||||
/// The effective accent color. When a separate dark color is enabled this is a
|
||||
/// dynamic platform color that resolves per trait environment, so all consumers
|
||||
/// (root tint and direct readers) adapt to light/dark automatically.
|
||||
var resolvedAccentColor: Color {
|
||||
let light = resolvedLightAccentColor
|
||||
guard useSeparateDarkAccentColor else { return light }
|
||||
let dark = resolvedDarkAccentColor
|
||||
#if os(macOS)
|
||||
return Color(nsColor: NSColor(name: nil, dynamicProvider: { appearance in
|
||||
let isDark = appearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua
|
||||
return NSColor(isDark ? dark : light)
|
||||
}))
|
||||
#else
|
||||
return Color(uiColor: UIColor { traits in
|
||||
UIColor(traits.userInterfaceStyle == .dark ? dark : light)
|
||||
})
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - App Icon Settings
|
||||
|
||||
var appIcon: AppIcon {
|
||||
|
||||
@@ -29,6 +29,9 @@ final class SettingsManager {
|
||||
var _theme: AppTheme?
|
||||
var _accentColor: AccentColor?
|
||||
var _customAccentColor: String?
|
||||
var _accentColorDark: AccentColor?
|
||||
var _customAccentColorDark: String?
|
||||
var _useSeparateDarkAccentColor: Bool?
|
||||
var _showWatchedCheckmark: Bool?
|
||||
|
||||
// Playback
|
||||
@@ -419,6 +422,9 @@ final class SettingsManager {
|
||||
_theme = nil
|
||||
_accentColor = nil
|
||||
_customAccentColor = nil
|
||||
_accentColorDark = nil
|
||||
_customAccentColorDark = nil
|
||||
_useSeparateDarkAccentColor = nil
|
||||
_showWatchedCheckmark = nil
|
||||
_preferredQuality = nil
|
||||
_cellularQuality = nil
|
||||
|
||||
Reference in New Issue
Block a user