mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
Add custom accent color with system color picker
Add a Custom swatch to the appearance settings accent color grid, backed by a hex value stored in settings (synced via iCloud like the rest). iOS uses the native ColorPicker wheel; macOS uses a circular swatch that opens NSColorPanel, since the SwiftUI color well looks out of place among the circles. All accent consumers now read the resolved color through SettingsManager.resolvedAccentColor. The indigo preset is retired from the grid but kept in the enum, so users who selected it keep their color until they pick another one. Claude-Session: https://claude.ai/code/session_0154KH8RAVAvm6iVanhmoW8o
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
struct AppearanceSettingsView: View {
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
@@ -146,13 +149,17 @@ private struct AccentColorSection: View {
|
||||
var body: some View {
|
||||
SettingsFormSection("settings.appearance.accentColor.header") {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 50))], spacing: 16) {
|
||||
ForEach(AccentColor.allCases, id: \.self) { accentColor in
|
||||
ForEach(AccentColor.presets, id: \.self) { accentColor in
|
||||
AccentColorButton(
|
||||
accentColor: accentColor,
|
||||
isSelected: settings.accentColor == accentColor,
|
||||
onSelect: { settings.accentColor = accentColor }
|
||||
)
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
CustomAccentColorButton(settings: settings)
|
||||
#endif
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
@@ -222,6 +229,110 @@ private struct AccentColorButton: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Custom Accent Color Button
|
||||
|
||||
#if !os(tvOS)
|
||||
private struct CustomAccentColorButton: View {
|
||||
@Bindable var settings: SettingsManager
|
||||
|
||||
private var isSelected: Bool { settings.accentColor == .custom }
|
||||
|
||||
#if os(macOS)
|
||||
var body: some View {
|
||||
Button {
|
||||
settings.accentColor = .custom
|
||||
ColorPanelBridge.shared.open(with: NSColor(settings.customAccentColor)) { nsColor in
|
||||
settings.customAccentColor = Color(nsColor: nsColor)
|
||||
settings.accentColor = .custom
|
||||
}
|
||||
} label: {
|
||||
ZStack {
|
||||
Circle()
|
||||
.strokeBorder(
|
||||
AngularGradient(
|
||||
colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red],
|
||||
center: .center
|
||||
),
|
||||
lineWidth: 4
|
||||
)
|
||||
.frame(width: 40, height: 40)
|
||||
|
||||
Circle()
|
||||
.fill(settings.customAccentColor)
|
||||
.frame(width: 28, height: 28)
|
||||
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption.bold())
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(String(localized: "settings.appearance.accentColor.custom"))
|
||||
}
|
||||
#else
|
||||
private var pickedColor: Binding<Color> {
|
||||
Binding(
|
||||
get: { settings.customAccentColor },
|
||||
set: { newColor in
|
||||
settings.customAccentColor = newColor
|
||||
settings.accentColor = .custom
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(settings.customAccentColor)
|
||||
.frame(width: 40, height: 40)
|
||||
.opacity(isSelected ? 1 : 0)
|
||||
|
||||
ColorPicker(
|
||||
String(localized: "settings.appearance.accentColor.custom"),
|
||||
selection: pickedColor,
|
||||
supportsOpacity: false
|
||||
)
|
||||
.labelsHidden()
|
||||
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption.bold())
|
||||
.foregroundStyle(.white)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.accessibilityLabel(String(localized: "settings.appearance.accentColor.custom"))
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
/// Routes NSColorPanel target/action callbacks to the settings binding.
|
||||
/// NSColorPanel keeps only a weak target, so this must outlive the view.
|
||||
@MainActor
|
||||
private final class ColorPanelBridge: NSObject {
|
||||
static let shared = ColorPanelBridge()
|
||||
private var onChange: ((NSColor) -> Void)?
|
||||
|
||||
func open(with color: NSColor, onChange: @escaping (NSColor) -> Void) {
|
||||
self.onChange = onChange
|
||||
let panel = NSColorPanel.shared
|
||||
panel.showsAlpha = false
|
||||
panel.color = color
|
||||
panel.setTarget(self)
|
||||
panel.setAction(#selector(colorChanged(_:)))
|
||||
panel.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
@objc private func colorChanged(_ sender: NSColorPanel) {
|
||||
onChange?(sender.color)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// MARK: - Theme Display Names
|
||||
|
||||
extension AppTheme {
|
||||
@@ -251,6 +362,7 @@ extension AccentColor {
|
||||
case .blue: return String(localized: "settings.appearance.accentColor.blue")
|
||||
case .purple: return String(localized: "settings.appearance.accentColor.purple")
|
||||
case .indigo: return String(localized: "settings.appearance.accentColor.indigo")
|
||||
case .custom: return String(localized: "settings.appearance.accentColor.custom")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user