SwiftUI zeichnet Symbole in Listen und Formularen auf macOS in der Akzentfarbe des Systems — nicht in der des Fensters. `.tint` an der Wurzel half deshalb nur der Reiterleiste; die Widget-Liste blieb systemgrün neben einer blauen Leiste, obwohl beides derselben Einstellung folgen soll. Ein eigener Label-Stil setzt die Farbe am Symbol. Er vererbt sich über die Umgebung, also genügt eine Zeile an denselben vier Wurzeln, an denen auch der Farbton gesetzt wird: Panel, Popovers, Einstellungen, Einrichtung. Ein Symbol, das seine Farbe selbst mitbringt, behält sie — die innere Angabe schlägt die äußere. Der grüne Haken für „erteilt" bringt sie jetzt am Symbol mit statt am ganzen Label: Grün heißt dort „erteilt" und ist keine Geschmacksfrage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
3.7 KiB
Swift
98 lines
3.7 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() })
|
|
.tint(Onyx.Color.accent)
|
|
.labelStyle(.onyx))
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|