import AppKit import SwiftUI import CalendarProvider import WeatherProvider import AudioProvider /// Zeigt das Onboarding — einmal, beim ersten Start. /// /// Als eigenes Fenster und nicht im Panel: TCC blendet Berechtigungsdialoge /// stillschweigend aus, wenn keine App im Vordergrund steht, und eine /// `.accessory`-App mit ausgefahrenem Panel steht nicht im Vordergrund. Genau /// daran ist die Kalenderberechtigung schon einmal gescheitert. @MainActor final class OnboardingWindowController: NSObject, NSWindowDelegate { private static let doneKey = "onboarding.completed" private var window: NSWindow? private let defaults: UserDefaults init(defaults: UserDefaults = .standard) { self.defaults = defaults } var isDone: Bool { defaults.bool(forKey: Self.doneKey) } func showIfNeeded(calendarModel: CalendarModel, weatherModel: WeatherModel, mixer: AudioMixer, fanControl: FanControl, launchAtLogin: LaunchAtLogin) { guard !isDone else { return } show(calendarModel: calendarModel, weatherModel: weatherModel, mixer: mixer, fanControl: fanControl, launchAtLogin: launchAtLogin) } func show(calendarModel: CalendarModel, weatherModel: WeatherModel, mixer: AudioMixer, fanControl: FanControl, launchAtLogin: LaunchAtLogin) { // Solange dieses Fenster steht, gehört Onyx in den Dock und in den // Vordergrund — sonst erscheinen die Systemdialoge gar nicht. NSApp.setActivationPolicy(.regular) if window == nil { let hosting = NSHostingController( rootView: OnboardingView( calendarModel: calendarModel, weatherModel: weatherModel, mixer: mixer, fanControl: fanControl, launchAtLogin: launchAtLogin, onFinish: { [weak self] in self?.finish() })) let window = NSWindow(contentViewController: hosting) window.title = "Onyx" window.styleMask = [.titled, .closable] window.isReleasedWhenClosed = false window.delegate = self window.center() self.window = window } NSApp.activate(ignoringOtherApps: true) window?.makeKeyAndOrderFront(nil) } /// Auch das Schließen über den roten Knopf gilt als erledigt. Ein Fenster, /// das beim nächsten Start wiederkommt, weil man es weggeklickt hat, ist /// eine Zumutung. private func finish() { defaults.set(true, forKey: Self.doneKey) window?.close() } func windowWillClose(_ notification: Notification) { defaults.set(true, forKey: Self.doneKey) // Zurück in den Hintergrund — es sei denn, ein anderes Fenster steht // noch offen. if !NSApp.windows.contains(where: { $0 !== window && $0.isVisible }) { NSApp.setActivationPolicy(.accessory) } } }