Alle Kacheln, Popovers und Fenster auf die Designschicht umgestellt: rund hundert gesetzte Schriftgrößen und 159 gesetzte Abstände und Radien sind jetzt Token. Übrig bleibt `spacing: 0` — das ist eine Aussage, keine geratene Zahl. Die Einstellungen bekommen eine Seitenleiste statt sieben Reitern, deren deutsche Beschriftungen zusammen 880 Punkte brauchten. Panel-Token stehen nicht mehr in Fenstern: dort gilt Systemsemantik, damit der Hellmodus nicht schwarz auf schwarz endet. Neu ist ein Schaufenster, das alle Bauteile nebeneinander zeigt — ohne das lässt sich eine Änderung an einem Token nur prüfen, indem man die App bedient, bis jede Kachel einmal zu sehen war. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
3.5 KiB
Swift
86 lines
3.5 KiB
Swift
import SwiftUI
|
|
import AppKit
|
|
import OnyxDesign
|
|
import OnyxWidgetKit
|
|
import OSLog
|
|
|
|
private let log = Logger(subsystem: "com.scarriffleservices.onyx", category: "AirDrop")
|
|
|
|
/// Der Weg zur Empfängerauswahl.
|
|
public enum AirDrop {
|
|
|
|
/// Öffnet AirDrop mit den übergebenen Dateien.
|
|
///
|
|
/// - Returns: ob AirDrop überhaupt zur Verfügung stand.
|
|
@MainActor
|
|
@discardableResult
|
|
public static func send(_ urls: [URL]) -> Bool {
|
|
guard !urls.isEmpty else { return false }
|
|
guard let service = NSSharingService(named: .sendViaAirDrop) else {
|
|
log.error("AirDrop steht nicht zur Verfügung")
|
|
return false
|
|
}
|
|
guard service.canPerform(withItems: urls) else {
|
|
log.error("AirDrop kann diese Auswahl nicht senden")
|
|
return false
|
|
}
|
|
service.perform(withItems: urls)
|
|
return true
|
|
}
|
|
}
|
|
|
|
/// Eine Fläche, auf die man Dateien fallen lässt, um sie zu verschicken.
|
|
public struct AirDropView: View {
|
|
/// Schmal, wenn sie sich die Kachel mit der Ablage teilt.
|
|
private let compact: Bool
|
|
@State private var isTargeted = false
|
|
@State private var failed = false
|
|
|
|
public init(compact: Bool = false) {
|
|
self.compact = compact
|
|
}
|
|
|
|
public var body: some View {
|
|
Group {
|
|
if compact {
|
|
// Nebeneinander statt übereinander: in 52 Punkten Höhe ist für
|
|
// Symbol über Text kein Platz.
|
|
HStack(spacing: Onyx.Space.md) {
|
|
Image(systemName: "shareplay")
|
|
.font(.system(size: Onyx.Symbol.large, weight: .light))
|
|
.foregroundStyle(isTargeted ? Onyx.Color.accent
|
|
: Onyx.Color.textTertiary)
|
|
Text(failed ? "airdrop.unavailable" : "airdrop.hint", bundle: .module)
|
|
.font(Onyx.Font.caption)
|
|
.foregroundStyle(failed ? Onyx.Color.warning
|
|
: Onyx.Color.textSecondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
Spacer(minLength: 0)
|
|
}
|
|
.padding(.horizontal, Onyx.Space.md)
|
|
} else {
|
|
VStack(spacing: Onyx.Space.sm) {
|
|
Image(systemName: "shareplay")
|
|
.font(.system(size: Onyx.Symbol.xlarge, weight: .light))
|
|
.foregroundStyle(isTargeted ? Onyx.Color.accent
|
|
: Onyx.Color.textTertiary)
|
|
Text(failed ? "airdrop.unavailable" : "airdrop.hint", bundle: .module)
|
|
.font(Onyx.Font.caption)
|
|
.foregroundStyle(failed ? Onyx.Color.warning
|
|
: Onyx.Color.textSecondary)
|
|
.multilineTextAlignment(.center)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
// Diese Fläche ist **nur** zum Ablegen da. Ihre Kante steht deshalb
|
|
// immer — ohne sie wüsste niemand, dass sie eine ist.
|
|
.onyxDropZone(isTargeted: isTargeted, style: .always)
|
|
// Derselbe Weg wie in der Ablage: auch hier kommen Anhänge als
|
|
// Versprechen an, nicht als fertige Datei.
|
|
.contentShape(.rect)
|
|
.onFileDrop(isTargeted: $isTargeted) { urls in failed = !AirDrop.send(urls) }
|
|
}
|
|
}
|