import SwiftUI // 发送预览表单:列出待发文件(名 + 大小)+ 选择目标设备 + 发送。手动选文件(TransferListView)与 // 分享交接(RootView)共用此一表单——即「选文件 → 预览 → 选设备 → 发送」。纯 UI:不感知文件来源 // (手动安全作用域 URL / 图库临时文件 / 分享 App Group),实际发送由调用方经 onSend(设备名) 实现 // (手动 loop engine.sendFile;分享 engine.sendShares),故同一表单复用零分叉。 struct PreviewFile: Identifiable { let id = UUID() let name: String let size: Int } struct SendComposeSheet: View { let files: [PreviewFile] let devices: [DeviceItem] let sendLabel: (DeviceItem) -> String let onSend: (String) -> Void // 目标设备名 let onCancel: () -> Void // 选中的目标设备名。onAppear 默认选第一个可达设备,减少一次点击。 @State private var selected = "" var body: some View { NavigationStack { Form { Section(t("ios.send.files")) { ForEach(files) { file in HStack(spacing: 10) { Image(systemName: "doc") .foregroundStyle(.secondary) Text(file.name) .lineLimit(1) Spacer() Text(formatBytes(file.size)) .font(.caption) .foregroundStyle(.secondary) } } } Section(t("ios.send.pickDevice")) { if devices.isEmpty { Text(t("ios.send.noDevices")) .foregroundStyle(.secondary) } else { ForEach(devices) { dev in Button { selected = dev.name } label: { HStack(spacing: 10) { Image(systemName: deviceSymbol(dev.type)) .foregroundStyle(dev.online ? Color.green : Color.secondary) Text(sendLabel(dev)) .foregroundStyle(.primary) Spacer() if selected == dev.name { Image(systemName: "checkmark") .foregroundStyle(Color.cdropAccent) } } } .buttonStyle(.plain) } } } } .navigationTitle(t("ios.send.previewTitle")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button(t("common.cancel")) { onCancel() } } ToolbarItem(placement: .topBarTrailing) { Button(t("ios.send.send")) { onSend(selected) } .fontWeight(.semibold) .disabled(selected.isEmpty || files.isEmpty) } } .onAppear { if selected.isEmpty { selected = devices.first?.name ?? "" } } } } } // 读文件字节大小(供发送预览展示)。安全作用域 URL(文件选择器)须临时声明访问才能读属性; // 临时文件 / App Group 文件无须声明(startAccessing 返回 false,仍可正常读),故统一走此函数。 func fileByteSize(_ url: URL) -> Int { let access = url.startAccessingSecurityScopedResource() defer { if access { url.stopAccessingSecurityScopedResource() } } return (try? url.resourceValues(forKeys: [ .fileSizeKey ]))?.fileSize ?? 0 }