Files
Commilitia-Drop/ios/CDrop/MacApp/MacRootView.swift
T
admin 512d7563ee 原生 macOS 引擎 + Broker 登录接通(Phase 3a)
- 引擎 / 鉴权跨平台 in-place(iOS 路径 #if 守卫、逐字不变,仅加 macOS/AppKit 分支):EngineController(import 守卫 + 剪贴板 UIPasteboard→NSPasteboard 上下行两处 + device_type 平台条件)、EngineWebView(UIViewRepresentable→typealias 统一后分 NSViewRepresentable)、Theme(动态色 Color(nsColor:dynamicProvider) 同 RGB)、DeviceNameStore(Host.current().localizedName)、PushRegistry(NSApplication.registerForRemoteNotifications)、Auth/BrokerLogin(presentationAnchor→NSApplication.keyWindow)、Auth/AuthManager(死 import UIKit 守卫)
- BackgroundTaskManager:整文件 #if os(iOS)(BGContinuedProcessingTask 为 API_UNAVAILABLE(macos)、编译级拒绝)+ macOS 同签名 no-op stub,使 EngineController.syncBackgroundTask 调用点无须改动
- device_type 修正(非机械):Mac 原生客户端 boot 注入 device_type=macos(与桌面 Wails 客户端一致,后端 / 各端已识别),不再硬编 ios——否则其他端设备列表图标错、且触发其 isWakeable 的 contains("ios") 误判本 Mac 可推送唤醒
- CommilitiaDropMac target 纳入「引擎 + 登录」最小集:Shared + Sources/Engine + Sources/Auth(除 iOS 专属 LoginView)+ RecordsStore / PushRegistry / LocalNetworkPermission / DeviceNameStore / BackgroundTaskManager + Resources/i18n;依赖 WebRTC;Info.plist 加 cdrop:// 回调 scheme(ASWebAuthenticationSession OAuth 回调)+ 本地网络(NSLocalNetworkUsageDescription / NSBonjourServices _cdrop._tcp)
- Mac 壳接引擎:CommilitiaDropMacApp(@State auth + engine,MacAppRoot 登录门 + 会话激活分桶记录)、MacLoginView(Broker 账户登录,免相机)、MacRootView(背景挂离屏引擎 WebView 保活 + 设备真实 presence 列表 + 设置账号/登出/引擎日志)、MacMenuContent(账号 + 在线设备数)。传输/文件/消息详情暂占位,随 Phase 3b 复用 iOS 视图
- 验证:just mac-build(CommilitiaDropMac.app)+ iOS 模拟器(CommilitiaDrop.app)均 BUILD SUCCEEDED。运行期登录 / 引擎联通待真机自测(Broker OAuth 需账号交互)
2026-07-07 22:22:30 +08:00

230 lines
6.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#if os(macOS)
import SwiftUI
// NavigationSplitView / / / / + NavigationStack
// iOS WebView iOS RootView / /
// Phase 3b iOS / presence / / 3a
// i18nt() 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