Die Einstellungen zeigten weiter "Anfragen", obwohl die Ortung längst lief: authorizationStatus ist eine Abfrage, keine Beobachtung, und ich hatte sie einmalig beim Öffnen gelesen. Jetzt meldet locationManagerDidChangeAuthorization die Änderung, WeatherModel führt daraus einen beobachtbaren Wert, und die Einstellungen aktualisieren sich, während das Fenster offen steht. Nach der Zustimmung wird sofort geladen statt beim nächsten Anlauf. Der Knopf "Systemeinstellungen öffnen" ganz unten zeigte fest auf den Kalenderbereich — wer ihn beim Standort drückte, landete am falschen Ort. Es gibt ihn nicht mehr; stattdessen führt jede Berechtigung in ihren eigenen Bereich, und der Knopf erscheint nur im Zustand "abgelehnt". Bei "noch nie gefragt" steht dort Anfragen, bei "erteilt" gar nichts: drei Zustände, drei Antworten. Genau daran ist die erste Fassung gescheitert. Dock-Symbol ist jetzt eine Einstellung, standardmäßig aus. Onyx läuft als LSUIElement — das war Absicht und keine Auslassung: es hat kein Hauptfenster und wäre im Dock ein Symbol, das beim Anklicken nichts öffnet, und es stünde im Programmumschalter zwischen den Programmen, mit denen man arbeitet. Wer es trotzdem will, schaltet es ein; die Wahl wird beim Start wieder angewendet.
116 lines
4.0 KiB
Swift
116 lines
4.0 KiB
Swift
import SwiftUI
|
|
import AppKit
|
|
import OnyxNotch
|
|
import OnyxWidgetKit
|
|
|
|
/// Der geteilte Zustand von Onyx: was im Panel liegt und auf welchen Displays.
|
|
///
|
|
/// Änderungen wirken sofort. Eine Einstellung, die erst nach einem Neustart
|
|
/// greift, ist in einer App ohne Fenster praktisch unauffindbar — man sieht ja
|
|
/// nicht, dass etwas passiert ist.
|
|
@MainActor
|
|
@Observable
|
|
final class AppModel {
|
|
|
|
private let store: LayoutStore
|
|
private let defaults: UserDefaults
|
|
|
|
/// Wird gerufen, wenn sich die Panelgröße geändert haben könnte.
|
|
var onLayoutChanged: (([WidgetPlacement]) -> Void)?
|
|
/// Wird gerufen, wenn Panels auf anderen Displays erscheinen sollen.
|
|
var onDisplayPolicyChanged: ((DisplayPolicy) -> Void)?
|
|
|
|
var layout: [WidgetPlacement] {
|
|
didSet {
|
|
guard layout != oldValue else { return }
|
|
persistLayout()
|
|
onLayoutChanged?(layout)
|
|
}
|
|
}
|
|
|
|
var displayPolicy: DisplayPolicy {
|
|
didSet {
|
|
guard displayPolicy != oldValue else { return }
|
|
defaults.set(displayPolicy.rawValue, forKey: Keys.displayPolicy)
|
|
onDisplayPolicyChanged?(displayPolicy)
|
|
}
|
|
}
|
|
|
|
var showsMenuBarIcon: Bool {
|
|
didSet { defaults.set(showsMenuBarIcon, forKey: Keys.showsMenuBarIcon) }
|
|
}
|
|
|
|
/// Ob Onyx im Dock auftaucht.
|
|
///
|
|
/// Standardmäßig nicht: Onyx ist ein Werkzeug, das in der Notch und in der
|
|
/// Menüleiste lebt, hat kein Hauptfenster und wäre im Dock ein Symbol, das
|
|
/// beim Anklicken nichts öffnet. Es taucht dann auch im Programmumschalter
|
|
/// auf und steht dort zwischen den Programmen, mit denen man arbeitet.
|
|
var showsDockIcon: Bool {
|
|
didSet {
|
|
guard showsDockIcon != oldValue else { return }
|
|
defaults.set(showsDockIcon, forKey: Keys.showsDockIcon)
|
|
NSApp.setActivationPolicy(showsDockIcon ? .regular : .accessory)
|
|
}
|
|
}
|
|
|
|
private enum Keys {
|
|
static let displayPolicy = "onyx.displayPolicy"
|
|
static let showsMenuBarIcon = "onyx.showsMenuBarIcon"
|
|
static let showsDockIcon = "onyx.showsDockIcon"
|
|
}
|
|
|
|
init(store: LayoutStore = .standard(), defaults: UserDefaults = .standard) {
|
|
self.store = store
|
|
self.defaults = defaults
|
|
|
|
let known = Set(WidgetRegistry.shared.all.map(\.id))
|
|
self.layout = store.load(knownWidgetIDs: known)
|
|
|
|
self.displayPolicy = defaults.string(forKey: Keys.displayPolicy)
|
|
.flatMap(DisplayPolicy.init(rawValue:)) ?? .builtInOnly
|
|
|
|
// Standardmäßig sichtbar: das Menüleistensymbol ist der einzige Weg
|
|
// zurück, wenn die Notch einmal nicht reagiert.
|
|
self.showsMenuBarIcon = defaults.object(forKey: Keys.showsMenuBarIcon) as? Bool ?? true
|
|
self.showsDockIcon = defaults.object(forKey: Keys.showsDockIcon) as? Bool ?? false
|
|
}
|
|
|
|
// MARK: - Widgets
|
|
|
|
var panelContentSize: CGSize { WidgetGrid.panelSize(for: layout) }
|
|
|
|
func isInPanel(_ widgetID: String) -> Bool {
|
|
layout.contains { $0.widgetID == widgetID }
|
|
}
|
|
|
|
func add(_ widget: any OnyxWidget) {
|
|
guard !isInPanel(widget.id) else { return }
|
|
let size = widget.supportedSizes.first ?? .small
|
|
layout.append(WidgetPlacement(widgetID: widget.id, size: size))
|
|
}
|
|
|
|
func remove(_ widgetID: String) {
|
|
layout.removeAll { $0.widgetID == widgetID }
|
|
}
|
|
|
|
func setSize(_ size: WidgetSize, for widgetID: String) {
|
|
guard let index = layout.firstIndex(where: { $0.widgetID == widgetID }) else { return }
|
|
layout[index].size = size
|
|
}
|
|
|
|
func move(fromOffsets source: IndexSet, toOffset destination: Int) {
|
|
layout.move(fromOffsets: source, toOffset: destination)
|
|
}
|
|
|
|
private func persistLayout() {
|
|
do {
|
|
try store.save(layout)
|
|
} catch {
|
|
// Ein fehlgeschlagener Schreibvorgang darf die App nicht anhalten;
|
|
// beim nächsten Start greift dann das Standardlayout.
|
|
NSLog("Onyx: Layout konnte nicht gespeichert werden — \(error.localizedDescription)")
|
|
}
|
|
}
|
|
}
|