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: 8) { Image(systemName: "shareplay") .font(.system(size: 15, 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, 8) } else { VStack(spacing: 6) { Image(systemName: "shareplay") .font(.system(size: 20, 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) .background { RoundedRectangle(cornerRadius: 10, style: .continuous) .strokeBorder(isTargeted ? Onyx.Color.accent : Onyx.Color.hairline, style: StrokeStyle(lineWidth: 1, dash: [4, 3])) } // 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) } .animation(Onyx.Motion.value, value: isTargeted) } }