Нема описа

AppDelegate.swift 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. //
  2. // AppDelegate.swift
  3. // App for Indeed
  4. //
  5. // Created by Mql Mac 2 on 11/05/2026.
  6. //
  7. import Cocoa
  8. enum AppWindowConfiguration {
  9. static let defaultContentSize = NSSize(width: 1120, height: 800)
  10. static let minimumContentSize = NSSize(width: 1120, height: 800)
  11. @MainActor
  12. static func apply(to window: NSWindow) {
  13. MainWindowCloseBehavior.install(on: window)
  14. window.minSize = minimumContentSize
  15. window.isRestorable = false
  16. window.title = AppMarketingLinks.appDisplayName
  17. window.styleMask.insert(.fullSizeContentView)
  18. window.titlebarAppearsTransparent = true
  19. window.titleVisibility = .hidden
  20. window.isMovableByWindowBackground = true
  21. // Same as dashboard chrome — avoids a halo outside the frame with fullSizeContentView.
  22. window.backgroundColor = AppAppearanceManager.shared.windowChromeColor
  23. let targetContent = NSRect(origin: .zero, size: defaultContentSize)
  24. let targetFrame = window.frameRect(forContentRect: targetContent)
  25. var frame = targetFrame
  26. let current = window.frame
  27. frame.origin.x = current.midX - frame.width / 2
  28. frame.origin.y = current.midY - frame.height / 2
  29. window.setFrame(frame, display: false, animate: false)
  30. window.setContentSize(defaultContentSize)
  31. }
  32. @MainActor
  33. static func mainWindow(in app: NSApplication = .shared) -> NSWindow? {
  34. app.mainWindow
  35. ?? app.keyWindow
  36. ?? app.windows.first(where: { $0.isVisible && $0.canBecomeKey })
  37. }
  38. }
  39. @main
  40. class AppDelegate: NSObject, NSApplicationDelegate {
  41. /// Avoids hammering StoreKit when `didBecomeActive` fires in quick succession (e.g. after system sheets).
  42. private var lastSubscriptionRefreshAt: Date?
  43. func applicationWillFinishLaunching(_ notification: Notification) {
  44. AppLanguageManager.shared.applyStoredPreferenceOnLaunch()
  45. AppAppearanceManager.shared.apply()
  46. }
  47. func applicationDidFinishLaunching(_ aNotification: Notification) {
  48. AppRatingCoordinator.shared.start()
  49. NotificationCenter.default.addObserver(
  50. forName: NSApplication.didBecomeActiveNotification,
  51. object: nil,
  52. queue: .main
  53. ) { [weak self] _ in
  54. Task { @MainActor in
  55. guard let self else { return }
  56. let now = Date()
  57. if let last = self.lastSubscriptionRefreshAt, now.timeIntervalSince(last) < 2.5 {
  58. return
  59. }
  60. self.lastSubscriptionRefreshAt = now
  61. await SubscriptionStore.shared.refreshEntitlements(deep: true)
  62. }
  63. }
  64. // Initial StoreKit refresh runs on `LoadingViewController` before the dashboard is shown.
  65. NSApp.activate(ignoringOtherApps: true)
  66. applyDefaultWindowSize()
  67. }
  68. @MainActor
  69. private func applyDefaultWindowSize() {
  70. DispatchQueue.main.async { [weak self] in
  71. guard let self, let window = AppWindowConfiguration.mainWindow() else { return }
  72. AppWindowConfiguration.apply(to: window)
  73. window.center()
  74. window.makeKeyAndOrderFront(nil)
  75. // Layout can run after the first pass; enforce default size once more.
  76. DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
  77. guard self != nil, let window = AppWindowConfiguration.mainWindow() else { return }
  78. AppWindowConfiguration.apply(to: window)
  79. window.center()
  80. }
  81. }
  82. }
  83. func applicationWillTerminate(_ aNotification: Notification) {
  84. // Insert code here to tear down your application
  85. }
  86. func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
  87. // Opt out until real `NSWindowRestoration` is implemented (avoids null className restore logs).
  88. return false
  89. }
  90. /// Keep running in the Dock when the user closes the last window (standard single-window macOS apps).
  91. func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
  92. false
  93. }
  94. /// Clicking the Dock icon after closing the window should bring it back.
  95. func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
  96. guard !flag else { return true }
  97. let window = AppWindowConfiguration.mainWindow(in: sender)
  98. ?? sender.windows.first(where: { $0.canBecomeKey })
  99. window?.makeKeyAndOrderFront(self)
  100. sender.activate(ignoringOtherApps: true)
  101. return true
  102. }
  103. }
  104. // MARK: - Main window close (hide on red button, do not quit)
  105. @MainActor
  106. private enum MainWindowCloseBehavior {
  107. private final class WindowDelegate: NSObject, NSWindowDelegate {
  108. static let shared = WindowDelegate()
  109. func windowShouldClose(_ sender: NSWindow) -> Bool {
  110. sender.orderOut(nil)
  111. return false
  112. }
  113. }
  114. static func install(on window: NSWindow) {
  115. window.isReleasedWhenClosed = false
  116. if window.delegate !== WindowDelegate.shared {
  117. window.delegate = WindowDelegate.shared
  118. }
  119. }
  120. }