Berechtigungen zeigen ihren Zustand live, Dock-Symbol wird wählbar
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.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
import OnyxNotch
|
||||
import OnyxWidgetKit
|
||||
|
||||
@@ -39,9 +40,24 @@ final class AppModel {
|
||||
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) {
|
||||
@@ -57,6 +73,7 @@ final class AppModel {
|
||||
// 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
|
||||
|
||||
@@ -1,491 +1,523 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"menu.openPanel" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Panel öffnen"
|
||||
"sourceLanguage": "en",
|
||||
"strings": {
|
||||
"menu.openPanel": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Panel öffnen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Open Panel"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Open Panel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu.quit" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Onyx beenden"
|
||||
"menu.quit": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Onyx beenden"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Quit Onyx"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Quit Onyx"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"menu.settings" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Einstellungen …"
|
||||
"menu.settings": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Einstellungen …"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Settings…"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Settings…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.all" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Allen Displays"
|
||||
"settings.display.all": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Allen Displays"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "All displays"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "All displays"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.builtInOnly" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nur dem internen Display"
|
||||
"settings.display.builtInOnly": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Nur dem internen Display"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Built-in display only"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Built-in display only"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.hint" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Auf Displays ohne echte Notch erscheint oben mittig ein Balken, der sich genauso verhält. Ist der Deckel geschlossen und kein internes Display vorhanden, erscheint das Panel trotzdem auf einem externen — sonst wäre Onyx nicht erreichbar."
|
||||
"settings.display.hint": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Auf Displays ohne echte Notch erscheint oben mittig ein Balken, der sich genauso verhält. Ist der Deckel geschlossen und kein internes Display vorhanden, erscheint das Panel trotzdem auf einem externen — sonst wäre Onyx nicht erreichbar."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "On displays without a real notch, a bar appears at the top centre and behaves the same. With the lid closed and no built-in display present, the panel still appears on an external one — otherwise Onyx would be unreachable."
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "On displays without a real notch, a bar appears at the top centre and behaves the same. With the lid closed and no built-in display present, the panel still appears on an external one — otherwise Onyx would be unreachable."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.menuBarIcon" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Symbol in der Menüleiste anzeigen"
|
||||
"settings.display.menuBarIcon": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Symbol in der Menüleiste anzeigen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Show icon in the menu bar"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Show icon in the menu bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.menuBarIcon.hint" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Über das Symbol erreichst du Panel, Einstellungen und Beenden. Ohne es kommst du nur über die Notch an Onyx heran."
|
||||
"settings.display.menuBarIcon.hint": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Über das Symbol erreichst du Panel, Einstellungen und Beenden. Ohne es kommst du nur über die Notch an Onyx heran."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "The icon gives you the panel, settings and quit. Without it, the notch is the only way to reach Onyx."
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "The icon gives you the panel, settings and quit. Without it, the notch is the only way to reach Onyx."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.policy" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Panel anzeigen auf"
|
||||
"settings.display.policy": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Panel anzeigen auf"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Show panel on"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Show panel on"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.permissions.calendar" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kalender"
|
||||
"settings.permissions.calendar": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Kalender"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Calendar"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Calendar"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.permissions.calendar.why" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Onyx zeigt deine Termine im Kalender-Widget an. Es wird ausschließlich gelesen, nie geschrieben."
|
||||
"settings.permissions.calendar.why": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Onyx zeigt deine Termine im Kalender-Widget an. Es wird ausschließlich gelesen, nie geschrieben."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Onyx shows your events in the calendar widget. It only ever reads, never writes."
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Onyx shows your events in the calendar widget. It only ever reads, never writes."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.permissions.granted" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Erteilt"
|
||||
"settings.permissions.granted": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Erteilt"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Granted"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Granted"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.permissions.location" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Standort"
|
||||
"settings.permissions.location": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Standort"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Location"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Location"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.permissions.location.why" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Onyx zeigt das Wetter an deinem aktuellen Standort. Ohne Zugriff kannst du stattdessen einen festen Ort wählen."
|
||||
"settings.permissions.location.why": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Onyx zeigt das Wetter an deinem aktuellen Standort. Ohne Zugriff kannst du stattdessen einen festen Ort wählen."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Onyx shows the weather for your current location. Without access you can pick a fixed place instead."
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Onyx shows the weather for your current location. Without access you can pick a fixed place instead."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.permissions.openSystem" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Systemeinstellungen öffnen"
|
||||
"settings.permissions.openSystem": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Systemeinstellungen öffnen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Open System Settings"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Open System Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.permissions.request" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Anfragen"
|
||||
"settings.permissions.request": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Anfragen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Request"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Request"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.size.large" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Groß (2×2)"
|
||||
"settings.size.large": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Groß (2×2)"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Large (2×2)"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Large (2×2)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.size.medium" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mittel (2×1)"
|
||||
"settings.size.medium": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Mittel (2×1)"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Medium (2×1)"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Medium (2×1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.size.small" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Klein (1×1)"
|
||||
"settings.size.small": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Klein (1×1)"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Small (1×1)"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Small (1×1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.size.wide" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Breit (4×1)"
|
||||
"settings.size.wide": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Breit (4×1)"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wide (4×1)"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Wide (4×1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.tab.display" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Anzeige"
|
||||
"settings.tab.display": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Anzeige"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Display"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Display"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.tab.permissions" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Berechtigungen"
|
||||
"settings.tab.permissions": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Permissions"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Permissions"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.tab.widgets" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Widgets"
|
||||
"settings.tab.widgets": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Widgets"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Widgets"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Widgets"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.widgets.add" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Hinzufügen"
|
||||
"settings.widgets.add": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Hinzufügen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Add"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Add"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.widgets.available" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Verfügbar"
|
||||
"settings.widgets.available": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Verfügbar"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Available"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Available"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.widgets.empty" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Keine Widgets im Panel. Das Panel bleibt leer, bis du eins hinzufügst."
|
||||
"settings.widgets.empty": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Keine Widgets im Panel. Das Panel bleibt leer, bis du eins hinzufügst."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No widgets in the panel. It stays empty until you add one."
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "No widgets in the panel. It stays empty until you add one."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.widgets.hint" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Die Reihenfolge bestimmt die Anordnung im Panel. Jedes Widget rückt an die erste Stelle, an die es passt."
|
||||
"settings.widgets.hint": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Die Reihenfolge bestimmt die Anordnung im Panel. Jedes Widget rückt an die erste Stelle, an die es passt."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "The order determines the arrangement in the panel. Each widget takes the first spot it fits into."
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "The order determines the arrangement in the panel. Each widget takes the first spot it fits into."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.widgets.inPanel" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Im Panel"
|
||||
"settings.widgets.inPanel": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Im Panel"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "In the panel"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "In the panel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.widgets.remove" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aus dem Panel entfernen"
|
||||
"settings.widgets.remove": {
|
||||
"extractionState": "stale",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Aus dem Panel entfernen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Remove from panel"
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Remove from panel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.dockIcon": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Symbol im Dock anzeigen"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Show icon in the Dock"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.display.dockIcon.hint": {
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Standardmäßig aus. Onyx lebt in der Notch und in der Menüleiste und hat kein Hauptfenster — im Dock wäre es ein Symbol, das beim Anklicken nichts öffnet, und es stünde im Programmumschalter zwischen den Programmen, mit denen du arbeitest."
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Off by default. Onyx lives in the notch and the menu bar and has no main window — in the Dock it would be an icon that opens nothing, and it would sit in the app switcher among the apps you actually work with."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
"version": "1.0"
|
||||
}
|
||||
@@ -52,6 +52,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
let model = AppModel()
|
||||
self.model = model
|
||||
// Die gespeicherte Wahl gleich anwenden — sonst steht Onyx nach einem
|
||||
// Neustart wieder ohne Dock-Symbol da, obwohl es eingeschaltet ist.
|
||||
NSApp.setActivationPolicy(model.showsDockIcon ? .regular : .accessory)
|
||||
|
||||
let coordinator = NotchCoordinator(policy: model.displayPolicy) { _, presentation in
|
||||
NotchHostingView(
|
||||
|
||||
@@ -42,96 +42,108 @@ struct SettingsView: View {
|
||||
private struct PermissionSettings: View {
|
||||
let calendarModel: CalendarModel
|
||||
let weatherModel: WeatherModel
|
||||
@State private var granted = false
|
||||
@State private var locationGranted = false
|
||||
@State private var calendarGranted = false
|
||||
|
||||
private var locationState: PermissionRow.State {
|
||||
switch weatherModel.locationAuthorization {
|
||||
case .granted: .granted
|
||||
case .denied: .denied
|
||||
case .undetermined: .undetermined
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
HStack {
|
||||
Label("settings.permissions.calendar", systemImage: "calendar")
|
||||
Spacer()
|
||||
if granted {
|
||||
Label("settings.permissions.granted", systemImage: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
.labelStyle(.titleAndIcon)
|
||||
} else {
|
||||
Button("settings.permissions.request") {
|
||||
Task {
|
||||
let ok = await calendarModel.requestAccessNow()
|
||||
granted = ok
|
||||
// macOS fragt nach einer Ablehnung nicht erneut.
|
||||
// Dann hilft nur noch der Weg über die
|
||||
// Systemeinstellungen — also dorthin führen.
|
||||
if !ok { openPrivacySettings() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text("settings.permissions.calendar.why")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
PermissionRow(
|
||||
title: "settings.permissions.calendar",
|
||||
symbol: "calendar",
|
||||
state: calendarGranted ? .granted
|
||||
: (calendarModel.authorization == .denied ? .denied : .undetermined),
|
||||
explanation: "settings.permissions.calendar.why",
|
||||
pane: .calendar,
|
||||
request: { calendarGranted = await calendarModel.requestAccessNow() })
|
||||
}
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
Label("settings.permissions.location", systemImage: "location")
|
||||
Spacer()
|
||||
if locationGranted {
|
||||
Label("settings.permissions.granted", systemImage: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
.labelStyle(.titleAndIcon)
|
||||
} else if weatherModel.locationIsDenied {
|
||||
Button("settings.permissions.openSystem") { openLocationSettings() }
|
||||
} else {
|
||||
Button("settings.permissions.request") {
|
||||
// Muss aus diesem Fenster kommen: macOS zeigt den
|
||||
// Dialog nur, wenn eine App im Vordergrund ist.
|
||||
weatherModel.requestLocationAuthorization()
|
||||
}
|
||||
}
|
||||
}
|
||||
Text("settings.permissions.location.why")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
Section {
|
||||
Button("settings.permissions.openSystem") { openPrivacySettings() }
|
||||
PermissionRow(
|
||||
title: "settings.permissions.location",
|
||||
symbol: "location",
|
||||
// Direkt aus dem beobachtbaren Wert: der Zustand ändert
|
||||
// sich, während dieses Fenster offen steht.
|
||||
state: locationState,
|
||||
explanation: "settings.permissions.location.why",
|
||||
pane: .location,
|
||||
request: { weatherModel.requestLocationAuthorization() })
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.padding()
|
||||
.task {
|
||||
granted = calendarModel.hasAccess
|
||||
guard !granted, !didAutoRequest,
|
||||
calendarModel.authorization == .undetermined else { return }
|
||||
didAutoRequest = true
|
||||
|
||||
// Kurz warten, bis das Fenster wirklich vorne steht. TCC zeigt den
|
||||
// Dialog nur, wenn eine App im Vordergrund ist, an die er gehören
|
||||
// kann — und Aktivierung wirkt asynchron. Ohne diese Pause fragt
|
||||
// Onyx, während es formal noch im Hintergrund liegt, und die
|
||||
// Anfrage verpufft kommentarlos.
|
||||
calendarGranted = calendarModel.hasAccess
|
||||
guard !calendarGranted, calendarModel.authorization == .undetermined else { return }
|
||||
// Kurz warten, bis das Fenster wirklich vorne steht: TCC zeigt den
|
||||
// Dialog nur, wenn eine App im Vordergrund ist, und Aktivierung
|
||||
// wirkt asynchron.
|
||||
try? await Task.sleep(for: .milliseconds(400))
|
||||
granted = await calendarModel.requestAccessNow()
|
||||
calendarGranted = await calendarModel.requestAccessNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Eine Berechtigung mit ihrem Zustand und dem Weg, der von dort weiterführt.
|
||||
///
|
||||
/// Die drei Zustände brauchen drei verschiedene Antworten, und genau daran ist
|
||||
/// die erste Fassung gescheitert: „noch nie gefragt" lässt sich mit einer
|
||||
/// Anfrage lösen, „abgelehnt" nur über die Systemeinstellungen, und „erteilt"
|
||||
/// braucht gar nichts.
|
||||
private struct PermissionRow: View {
|
||||
enum State { case granted, denied, undetermined }
|
||||
enum Pane {
|
||||
case calendar, location
|
||||
|
||||
/// Jede Berechtigung führt in ihren eigenen Bereich. Ein gemeinsamer
|
||||
/// Knopf, der immer im Kalender landet, ist schlimmer als keiner.
|
||||
var url: URL? {
|
||||
switch self {
|
||||
case .calendar:
|
||||
URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars")
|
||||
case .location:
|
||||
URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_LocationServices")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@State private var didAutoRequest = false
|
||||
let title: LocalizedStringKey
|
||||
let symbol: String
|
||||
let state: State
|
||||
let explanation: LocalizedStringKey
|
||||
let pane: Pane
|
||||
let request: () async -> Void
|
||||
|
||||
private func openLocationSettings() {
|
||||
guard let url = URL(string:
|
||||
"x-apple.systempreferences:com.apple.preference.security?Privacy_LocationServices") else { return }
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
|
||||
private func openPrivacySettings() {
|
||||
guard let url = URL(string:
|
||||
"x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars") else { return }
|
||||
NSWorkspace.shared.open(url)
|
||||
var body: some View {
|
||||
HStack {
|
||||
Label(title, systemImage: symbol)
|
||||
Spacer()
|
||||
switch state {
|
||||
case .granted:
|
||||
Label("settings.permissions.granted", systemImage: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
.labelStyle(.titleAndIcon)
|
||||
case .undetermined:
|
||||
Button("settings.permissions.request") { Task { await request() } }
|
||||
case .denied:
|
||||
// macOS fragt nach einer Ablehnung nicht mehr — ein
|
||||
// Anfragen-Knopf wäre hier eine Lüge.
|
||||
Button("settings.permissions.openSystem") {
|
||||
if let url = pane.url { NSWorkspace.shared.open(url) }
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(explanation)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +255,14 @@ private struct DisplaySettings: View {
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Divider()
|
||||
|
||||
Toggle("settings.display.dockIcon", isOn: $model.showsDockIcon)
|
||||
Text("settings.display.dockIcon.hint")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.padding()
|
||||
|
||||
Reference in New Issue
Block a user