Sen descrición

AppAppearanceManager.swift 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. //
  2. // AppAppearanceManager.swift
  3. // App for Indeed
  4. //
  5. import AppKit
  6. /// Persists and applies the user’s light / dark / system appearance preference.
  7. @MainActor
  8. final class AppAppearanceManager {
  9. static let shared = AppAppearanceManager()
  10. static let didChangeNotification = Notification.Name("AppAppearanceManager.didChange")
  11. enum Mode: String, CaseIterable {
  12. case light
  13. case dark
  14. case system
  15. var segmentIndex: Int {
  16. switch self {
  17. case .system: 0
  18. case .light: 1
  19. case .dark: 2
  20. }
  21. }
  22. init?(segmentIndex: Int) {
  23. switch segmentIndex {
  24. case 0: self = .system
  25. case 1: self = .light
  26. case 2: self = .dark
  27. default: return nil
  28. }
  29. }
  30. }
  31. private enum UserDefaultsKey {
  32. static let appearanceMode = "com.appforindeed.appearanceMode"
  33. }
  34. private var systemThemeObserver: NSObjectProtocol?
  35. private init() {
  36. systemThemeObserver = DistributedNotificationCenter.default().addObserver(
  37. forName: Notification.Name("AppleInterfaceThemeChangedNotification"),
  38. object: nil,
  39. queue: .main
  40. ) { [weak self] _ in
  41. guard let self, self.mode == .system else { return }
  42. self.apply()
  43. NotificationCenter.default.post(name: Self.didChangeNotification, object: self)
  44. }
  45. }
  46. /// Whether the app is currently rendering with a dark appearance (respects System mode).
  47. var isDark: Bool {
  48. switch mode {
  49. case .light:
  50. false
  51. case .dark:
  52. true
  53. case .system:
  54. NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
  55. }
  56. }
  57. var mode: Mode {
  58. get {
  59. guard let raw = UserDefaults.standard.string(forKey: UserDefaultsKey.appearanceMode),
  60. let stored = Mode(rawValue: raw) else {
  61. return .system
  62. }
  63. return stored
  64. }
  65. set {
  66. guard newValue != mode else { return }
  67. UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.appearanceMode)
  68. apply()
  69. NotificationCenter.default.post(name: Self.didChangeNotification, object: self)
  70. }
  71. }
  72. /// Window backing color aligned with dashboard chrome for the active appearance.
  73. var windowChromeColor: NSColor {
  74. AppDashboardTheme.chromeBackground
  75. }
  76. func apply() {
  77. switch mode {
  78. case .light:
  79. NSApp.appearance = NSAppearance(named: .aqua)
  80. case .dark:
  81. NSApp.appearance = NSAppearance(named: .darkAqua)
  82. case .system:
  83. NSApp.appearance = nil
  84. }
  85. updateWindowChrome()
  86. }
  87. private func updateWindowChrome() {
  88. let color = windowChromeColor
  89. for window in NSApp.windows where window.isVisible || window.canBecomeKey {
  90. window.backgroundColor = color
  91. }
  92. }
  93. }