#if os(macOS) import SwiftUI // 桌面根界面:NavigationSplitView 侧栏(传输 / 文件 / 消息 / 设备 / 设置)+ 详情 NavigationStack, // 替代 iOS 的标签栏。离屏无头引擎 WebView 挂在背景保活(同 iOS RootView)。传输 / 文件 / 消息详情为 // 占位(随 Phase 3b 复用 iOS 视图);设备 / 设置已接引擎真实态(presence / 账号 / 日志),作 3a 引擎 // 联通证明。分区标签暂硬编中文;接入 i18n(t())待 Phase 3b 复用视图时统一。 struct MacRootView: View { @Environment(EngineController.self) private var engine @Environment(AuthManager.self) private var auth @State private var selection: MacSection = .transfers var body: some View { NavigationSplitView { List(selection: $selection) { brandHeader .listRowInsets(EdgeInsets(top: 10, leading: 8, bottom: 14, trailing: 8)) .listRowSeparator(.hidden) .selectionDisabled() Section { ForEach(MacSection.sidebar) { section in Label(section.title, systemImage: section.icon) .badge(badge(for: section)) .tag(section) } } } .navigationSplitViewColumnWidth(min: 200, ideal: 220, max: 280) .navigationTitle("Commilitia Drop") } detail: { NavigationStack { detail } } // 离屏无头引擎 WebView:零尺寸不可见,仅保持存活(登录后随本视图出现即 boot)。同 iOS RootView。 .background { EngineWebView(controller: engine, auth: auth) .frame(width: 0, height: 0) .opacity(0) .allowsHitTesting(false) } } @ViewBuilder private var detail: some View { switch selection { case .transfers: MacPlaceholder(section: .transfers) case .files: MacPlaceholder(section: .files) case .messages: MacPlaceholder(section: .messages) case .devices: MacDevicesView() case .settings: MacSettingsView() } } private var brandHeader: some View { HStack(spacing: 10) { Image(systemName: "paperplane.fill") .font(.title2) .foregroundStyle(.tint) Text("Commilitia Drop") .font(.title3.weight(.semibold)) .lineLimit(1) } } // 未读红点:消息 / 传输(对齐 iOS RootView 的 badge)。 private func badge(for section: MacSection) -> Int { switch section { case .messages: return engine.unreadMessages case .transfers: return engine.unreadTransfers default: return 0 } } } // 侧栏分区。与 iOS 标签一致:传输 / 文件 / 消息 / 设备 / 设置。 enum MacSection: Hashable, Identifiable, CaseIterable { case transfers, files, messages, devices, settings var id: Self { self } var title: String { switch self { case .transfers: return "传输" case .files: return "文件" case .messages: return "消息" case .devices: return "设备" case .settings: return "设置" } } var icon: String { switch self { case .transfers: return "arrow.up.arrow.down" case .files: return "folder" case .messages: return "bubble.left.and.bubble.right" case .devices: return "laptopcomputer.and.iphone" case .settings: return "gearshape" } } static var sidebar: [MacSection] { MacSection.allCases } } // 阶段占位详情(传输 / 文件 / 消息,随 Phase 3b 替换为复用 iOS 视图)。 struct MacPlaceholder: View { let section: MacSection var body: some View { ContentUnavailableView { Label(section.title, systemImage: section.icon) } description: { Text("即将接入(原生数据面复用 iOS 引擎,桌面功能适配中)。") } .navigationTitle(section.title) } } // 设备:引擎经桥推来的真实 presence(在线绿点 + 名称 + 类型),证明登录后引擎已联通信令 / SSE。 struct MacDevicesView: View { @Environment(EngineController.self) private var engine var body: some View { Group { if engine.devices.isEmpty { ContentUnavailableView("暂无其他设备", systemImage: "laptopcomputer.and.iphone", description: Text("同账户的其他在线设备将显示在此。")) } else { List(engine.devices) { dev in HStack(spacing: 10) { Circle() .fill(dev.online ? Color.green : Color.secondary) .frame(width: 9, height: 9) Text(dev.name) Spacer() Text(dev.type) .font(.caption) .foregroundStyle(.secondary) } } } } .navigationTitle("设备") } } // 设置:账号 + 本机设备名 + 登出 + 引擎日志(3a 联通与现场自检)。 struct MacSettingsView: View { @Environment(EngineController.self) private var engine @Environment(AuthManager.self) private var auth var body: some View { Form { Section("账号") { LabeledContent("用户", value: auth.session?.user.name ?? "—") LabeledContent("本机设备", value: engine.deviceName) Button("登出", role: .destructive) { engine.reset() auth.logout() } } Section("引擎日志") { if engine.logs.isEmpty { Text("暂无日志").foregroundStyle(.secondary) } else { ForEach(engine.logs.prefix(30)) { entry in Text(entry.message) .font(.caption.monospaced()) .foregroundStyle(.secondary) .lineLimit(2) .textSelection(.enabled) } } } } .formStyle(.grouped) .navigationTitle("设置") } } #endif