Nessuna descrizione

AppAppearanceManager.swift 2.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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. NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
  49. }
  50. var mode: Mode {
  51. get {
  52. guard let raw = UserDefaults.standard.string(forKey: UserDefaultsKey.appearanceMode),
  53. let stored = Mode(rawValue: raw) else {
  54. return .system
  55. }
  56. return stored
  57. }
  58. set {
  59. guard newValue != mode else { return }
  60. UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.appearanceMode)
  61. apply()
  62. NotificationCenter.default.post(name: Self.didChangeNotification, object: self)
  63. }
  64. }
  65. /// Window backing color aligned with dashboard chrome for the active appearance.
  66. var windowChromeColor: NSColor {
  67. AppDashboardTheme.chromeBackground
  68. }
  69. func apply() {
  70. switch mode {
  71. case .light:
  72. NSApp.appearance = NSAppearance(named: .aqua)
  73. case .dark:
  74. NSApp.appearance = NSAppearance(named: .darkAqua)
  75. case .system:
  76. NSApp.appearance = nil
  77. }
  78. updateWindowChrome()
  79. }
  80. private func updateWindowChrome() {
  81. let color = windowChromeColor
  82. for window in NSApp.windows where window.isVisible || window.canBecomeKey {
  83. window.backgroundColor = color
  84. }
  85. }
  86. }