diff --git a/punchnet/Core/VPNManager.swift b/punchnet/Core/VPNManager.swift index f12b30f..720646e 100644 --- a/punchnet/Core/VPNManager.swift +++ b/punchnet/Core/VPNManager.swift @@ -18,6 +18,7 @@ class VPNManager { private var manager: NETunnelProviderManager? private var statusObserver: NSObjectProtocol? + var isConnected: Bool = false var vpnStatus: VPNStatus = .disconnected var vpnStatusStream: AsyncStream @@ -68,12 +69,15 @@ class VPNManager { case .invalid, .disconnected, .disconnecting: self?.vpnStatusCont.yield(.disconnected) self?.vpnStatus = .disconnected + self?.isConnected = false case .connecting, .connected, .reasserting: self?.vpnStatusCont.yield(.connected) self?.vpnStatus = .connected + self?.isConnected = true @unknown default: self?.vpnStatusCont.yield(.disconnected) self?.vpnStatus = .disconnected + self?.isConnected = false } } } diff --git a/punchnet/Networking/SDLAPIClient+Network.swift b/punchnet/Networking/SDLAPIClient+Network.swift index 15f81d5..a3156da 100644 --- a/punchnet/Networking/SDLAPIClient+Network.swift +++ b/punchnet/Networking/SDLAPIClient+Network.swift @@ -88,7 +88,7 @@ extension SDLAPIClient { } static func `default`() -> Self { - return .init(ip: "", maskLen: 24, hostname: "", identityId: 0, resourceList: [], nodeList: []) + return .init(ip: "0.0.0.0", maskLen: 24, hostname: "", identityId: 0, resourceList: [], nodeList: []) } func getNode(id: Int?) -> Node? { diff --git a/punchnet/Views/AppContext.swift b/punchnet/Views/AppContext.swift index e7d730f..a9567ee 100644 --- a/punchnet/Views/AppContext.swift +++ b/punchnet/Views/AppContext.swift @@ -107,6 +107,11 @@ class AppContext { } } + // 断开网络连接 + func disconnectNetwork() async throws { + try await self.vpnManager.disableVpn() + } + // 退出登陆 func logout() async throws { try await self.vpnManager.disableVpn() diff --git a/punchnet/Views/Network/NetworkView.swift b/punchnet/Views/Network/NetworkView.swift index 9965a91..9cc082b 100644 --- a/punchnet/Views/Network/NetworkView.swift +++ b/punchnet/Views/Network/NetworkView.swift @@ -31,31 +31,7 @@ struct NetworkView: View { VStack(spacing: 0) { // 1. 头部区域 (Header) HStack(spacing: 16) { - ZStack { - Circle() - .fill(connectState == .connected ? Color.green.opacity(0.15) : Color.primary.opacity(0.05)) - .frame(width: 36, height: 36) - - Image(systemName: connectState == .connected ? "checkmark.shield.fill" : "shield.slash.fill") - .symbolRenderingMode(.hierarchical) - .foregroundStyle(connectState == .connected ? Color.green : Color.secondary) - .font(.system(size: 16)) - } - - VStack(alignment: .leading, spacing: 2) { - Text(appContext.networkSession?.networkName ?? "未连接网络") - .font(.system(size: 14, weight: .semibold)) - - if connectState == .connected { - Text("虚拟局域网 IP: \(self.appContext.networkContext.ip)") - .font(.system(size: 11, design: .monospaced)) - .foregroundColor(.secondary) - } else { - Text("PunchNet 服务未就绪") - .font(.caption) - .foregroundColor(.secondary) - } - } + NetworkStatusBar() Spacer() @@ -124,6 +100,47 @@ struct NetworkView: View { } +struct NetworkStatusBar: View { + @Environment(AppContext.self) private var appContext + @State private var vpnManger = VPNManager.shared + + var body: some View { + HStack(spacing: 12) { + // 左侧:状态指示器与文字 + HStack(spacing: 20) { + ZStack { + Circle() + .fill(vpnManger.isConnected ? Color.green.opacity(0.15) : Color.primary.opacity(0.05)) + .frame(width: 36, height: 36) + + Image(systemName: vpnManger.isConnected ? "checkmark.shield.fill" : "shield.slash.fill") + .symbolRenderingMode(.hierarchical) + .foregroundStyle(vpnManger.isConnected ? Color.green : Color.secondary) + .font(.system(size: 16)) + } + + VStack(alignment: .leading, spacing: 1) { + if let networkSession = appContext.networkSession { + Text(networkSession.networkName) + .font(.system(size: 12, weight: .semibold)) + + Text("局域网IP: \(appContext.networkContext.ip)") + .font(.system(size: 10, design: .monospaced)) + .foregroundColor(.secondary) + } + } + } + + // 右侧:Switch 开关 + // 注意:这里使用 Binding 手动接管连接/断开逻辑 + Toggle("", isOn: $vpnManger.isConnected) + .toggleStyle(.switch) + .controlSize(.small) // macOS 顶部栏或面板推荐使用 small 尺寸 + } + .padding(.vertical, 5) + } +} + struct NetworkConnectedView: View { @Environment(AppContext.self) private var appContext: AppContext @Binding var showMode: NetworkShowMode diff --git a/punchnet/Views/Settings/SettingsAccountView.swift b/punchnet/Views/Settings/SettingsAccountView.swift index 647f6fc..5d0bd4d 100644 --- a/punchnet/Views/Settings/SettingsAccountView.swift +++ b/punchnet/Views/Settings/SettingsAccountView.swift @@ -141,6 +141,7 @@ extension SettingsAccountView { Task { @MainActor in try await appContext.logout() } + self.appContext.appScene = .login(username: nil) self.dismissWindow(id: "settings") self.openWindow(id: "main")