Files
onyx/Onyx/OnboardingWindowController.swift
Scarriffle c94c6166f5 Der Farbwechsel kommt jetzt auch an
Farbton und Symbolfarbe standen beim **Erzeugen** der Fenster, also außerhalb
jeder View. Damit wird der Wert genau einmal gelesen und SwiftUI erfährt nie,
dass er sich geändert hat: die Reiter, Knöpfe und Symbole blieben in der
Farbe stehen, die beim Öffnen galt, während der Farbwähler daneben schon die
neue zeigte.

Jetzt stehen beide im Rumpf der jeweiligen View — Panel, Einstellungen,
Einrichtung. Dort registriert SwiftUI den Zugriff und zeichnet neu.

Bei den Popovers blieb es beim Erzeugen: die entstehen bei jedem Öffnen neu,
lesen also ohnehin den aktuellen Wert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:18:06 +02:00

96 lines
3.6 KiB
Swift

import AppKit
import SwiftUI
import OnyxDesign
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
// Nicht direkt unter die Notch: wer den Schließknopf trifft,
// fährt sonst durch die Auslösefläche.
if let screen = NSScreen.main {
let visible = screen.visibleFrame
window.setFrameOrigin(NSPoint(x: visible.midX - window.frame.width / 2,
y: visible.maxY - 60 - window.frame.height))
} else {
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)
hide()
}
/// Wie bei den Einstellungen: ausblenden statt schließen, damit ein
/// Hilfsprogramm mit letztes Fenster zu = App beenden" Onyx nicht mit
/// wegräumt.
func windowShouldClose(_ sender: NSWindow) -> Bool {
defaults.set(true, forKey: Self.doneKey)
hide()
return false
}
private func hide() {
window?.orderOut(nil)
// 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)
}
}
}