// // AirDropState.swift // MioIsland AirDrop Plugin // // Shared state + send coordinator. Single source of truth for what // the panel should render. Not strictly "ObservableObject" heavy — // we have only three phases, but using @Published keeps the door // open for future "sending progress" UI. // import AppKit import Combine import Foundation @MainActor final class AirDropState: ObservableObject { static let shared = AirDropState() enum Phase: Equatable { case idle case showingPicker case sent(count: Int) case error(message: String) } @Published var phase: Phase = .idle /// Last completed send — used to animate a brief "✓ 已发送" toast. @Published private(set) var lastSendAt: Date? /// Called by UI when user drops files or picks from NSOpenPanel. /// Kicks off NSSharingService (opens the AirDrop chooser window) /// and resets phase when the user dismisses the chooser. func send(files: [URL]) { let urls = files.filter { $0.isFileURL } guard !urls.isEmpty else { return } phase = .showingPicker AirDropService.perform(files: urls) { [weak self] result in guard let self else { return } Task { @MainActor in switch result { case .success: self.phase = .sent(count: urls.count) self.lastSendAt = Date() // Drop back to idle after 2.5s so the drop zone is // usable for the next file without a click. DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { [weak self] in guard let self else { return } if case .sent = self.phase { self.phase = .idle } } case .failure(let err): self.phase = .error(message: err.localizedDescription) DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in guard let self else { return } if case .error = self.phase { self.phase = .idle } } } } } } /// Open NSOpenPanel to let the user pick files manually, then send. func chooseAndSend() { let panel = NSOpenPanel() panel.allowsMultipleSelection = true panel.canChooseDirectories = true panel.canChooseFiles = true panel.message = L10n.chooseFilesTitle panel.prompt = L10n.choose // Run modal on the main thread so it doesn't race the drop zone. panel.begin { [weak self] response in guard response == .OK else { return } self?.send(files: panel.urls) } } }