diff --git a/ios/CDrop/MacApp/MacQuickSend.swift b/ios/CDrop/MacApp/MacQuickSend.swift index 4916ae3..a145d46 100644 --- a/ios/CDrop/MacApp/MacQuickSend.swift +++ b/ios/CDrop/MacApp/MacQuickSend.swift @@ -1,31 +1,66 @@ #if os(macOS) import AppKit -import Observation +import SwiftUI -// Quick Send:全局热键(MacHotkey)或菜单栏触发 → 激活 app + NSOpenPanel 选文件 → 主窗口切到「传输」区 -// 并弹发送表单选目标设备发送。pending 为待发文件,MacRootView 观察它、présente MacComposeSheet。 -@Observable +// Quick Send:全局热键(MacHotkey)或菜单栏触发 → 激活 app + NSOpenPanel 选文件 → 弹出独立发送面板 +// (自持 NSWindow 托 MacComposeSheet)选目标设备发送。**自持面板而非挂主窗口的 sheet**:Quick Send 的 +// 典型场景正是「关主窗口、仅菜单栏 / 热键常驻」,此时主窗口 MacRootView 未挂载、其 sheet 无处可弹(会 +// 静默失效)——故此处不依赖主窗口,直接以自己的窗口呈现发送表单(ultracode 审查修复)。 +@MainActor final class MacQuickSend { static let shared = MacQuickSend() private init() {} - var pending: [URL] = [] + private var panel: NSWindow? - // 激活 app + 选文件(主线程同步)。有选中即置 pending,触发主窗口的发送表单。未登录时 MacRootView - // 未挂载、表单不出现——Quick Send 仅登录态有意义(菜单项 / 热键均在登录后才提供 / 注册)。 + // 激活 app + 选文件(主线程同步)。有选中即弹发送面板;无可达设备则提示。 static func trigger() { NSApp.activate() - let panel = NSOpenPanel() - panel.canChooseFiles = true - panel.canChooseDirectories = false - panel.allowsMultipleSelection = true - panel.prompt = "发送" - if panel.runModal() == .OK, !panel.urls.isEmpty + let p = NSOpenPanel() + p.canChooseFiles = true + p.canChooseDirectories = false + p.allowsMultipleSelection = true + p.prompt = "发送" + guard p.runModal() == .OK, !p.urls.isEmpty else { return } + shared.present(urls: p.urls) + } + + private func present(urls: [URL]) + { + let engine = MacBackgroundController.shared.engine + guard !engine.sendableDevices().isEmpty else { - shared.pending = panel.urls + let alert = NSAlert() + alert.messageText = "暂无可达设备" + alert.informativeText = "请确保有其他在线设备(或可唤醒的 iOS 设备)。" + alert.runModal() + return } + // 复用 MacComposeSheet(待发文件 + 目标设备选择);发送 / 取消后关面板。 + let root = MacComposeSheet(urls: urls, + onCancel: { [weak self] in self?.close() }, + onSend: + { [weak self] device in + for url in urls { engine.sendFile(to: device, fileURL: url) } + self?.close() + }) + .environment(engine) + let win = NSWindow(contentViewController: NSHostingController(rootView: root)) + win.title = "快速发送" + win.styleMask = [ .titled, .closable ] + win.isReleasedWhenClosed = false + win.center() + win.makeKeyAndOrderFront(nil) + NSApp.activate() + panel = win + } + + private func close() + { + panel?.close() + panel = nil } } #endif diff --git a/ios/CDrop/MacApp/MacRootView.swift b/ios/CDrop/MacApp/MacRootView.swift index 04f6408..efa67a6 100644 --- a/ios/CDrop/MacApp/MacRootView.swift +++ b/ios/CDrop/MacApp/MacRootView.swift @@ -10,7 +10,6 @@ struct MacRootView: View { @Environment(EngineController.self) private var engine @Environment(AuthManager.self) private var auth - @State private var quickSend = MacQuickSend.shared @State private var selection: MacSection = .transfers var body: some View @@ -51,23 +50,7 @@ struct MacRootView: View { NoticeOverlay() } - // Quick Send(热键 / 菜单栏触发):有待发文件即切到「传输」并弹发送表单。 - .onChange(of: quickSend.pending) - { _, urls in - if !urls.isEmpty { selection = .transfers } - } - .sheet(isPresented: Binding(get: { !quickSend.pending.isEmpty }, - set: { if !$0 { quickSend.pending = [] } })) - { - MacComposeSheet(urls: quickSend.pending, - onCancel: { quickSend.pending = [] }, - onSend: - { device in - for url in quickSend.pending { engine.sendFile(to: device, fileURL: url) } - quickSend.pending = [] - }) - .environment(engine) - } + // Quick Send(热键 / 菜单栏触发)自持独立发送面板(见 MacQuickSend),不依赖本窗口,故此处无需挂载。 } @ViewBuilder @@ -155,7 +138,16 @@ struct MacDevicesView: View private var others: [DeviceItem] { - engine.devices.filter { $0.deviceID != engine.selfDeviceID } + engine.devices.filter { !isSelf($0) } + } + + // 判本机:优先按稳定 device_id,selfDeviceID 缺失时回落按设备名(对齐 iOS isSelf,防边角态下本机 + // 误入「其他设备」列表被右键「移除」而吊销自身会话)。 + private func isSelf(_ dev: DeviceItem) -> Bool + { + let selfID = engine.selfDeviceID + if !selfID.isEmpty, !dev.deviceID.isEmpty { return dev.deviceID == selfID } + return dev.name == engine.deviceName } var body: some View