From c5b2cb3e83dc32318dde957a6f8227203c8c4284 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Fri, 17 Apr 2026 16:06:51 +0800 Subject: [PATCH] fix views --- .../Network/ViewModels/NetworkModel.swift | 14 ++ .../Network/Views/NetworkConnectedView.swift | 8 +- .../Network/Views/NetworkStatusBar.swift | 157 ------------------ .../Features/Network/Views/NetworkView.swift | 138 ++++++++++++++- 4 files changed, 155 insertions(+), 162 deletions(-) delete mode 100644 punchnet/Features/Network/Views/NetworkStatusBar.swift diff --git a/punchnet/Features/Network/ViewModels/NetworkModel.swift b/punchnet/Features/Network/ViewModels/NetworkModel.swift index 9f73ea6..13387d1 100644 --- a/punchnet/Features/Network/ViewModels/NetworkModel.swift +++ b/punchnet/Features/Network/ViewModels/NetworkModel.swift @@ -415,3 +415,17 @@ final class NetworkModel { return "\(context.identityId)-\(context.ip)" } } + +extension NetworkModel { + struct ExitNodeOption: Identifiable, Equatable { + let id: Int + let nodeName: String + let ip: String + let system: String? + + var nodeNameWithIp: String { + "\(nodeName) (\(ip))" + } + } + +} diff --git a/punchnet/Features/Network/Views/NetworkConnectedView.swift b/punchnet/Features/Network/Views/NetworkConnectedView.swift index 1edb4eb..38c9453 100644 --- a/punchnet/Features/Network/Views/NetworkConnectedView.swift +++ b/punchnet/Features/Network/Views/NetworkConnectedView.swift @@ -35,7 +35,7 @@ struct NetworkConnectedView: View { } // MARK: - 设备组视图 (NavigationSplitView) -struct NetworkDeviceGroupView: View { +private struct NetworkDeviceGroupView: View { var model: NetworkModel // 侧边栏宽度 @@ -90,7 +90,7 @@ struct NetworkDeviceGroupView: View { } // MARK: - 子组件 -struct NetworkNodeHeadView: View { +private struct NetworkNodeHeadView: View { var node: NetworkContext.Node var body: some View { @@ -112,7 +112,7 @@ struct NetworkNodeHeadView: View { } } -struct NetworkNodeDetailView: View { +private struct NetworkNodeDetailView: View { var model: NetworkModel var node: NetworkContext.Node @@ -152,7 +152,7 @@ struct NetworkNodeDetailView: View { } } -struct ResourceItemCard: View { +private struct ResourceItemCard: View { let resource: NetworkContext.Resource @State private var isHovered = false diff --git a/punchnet/Features/Network/Views/NetworkStatusBar.swift b/punchnet/Features/Network/Views/NetworkStatusBar.swift deleted file mode 100644 index 12b8aa3..0000000 --- a/punchnet/Features/Network/Views/NetworkStatusBar.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// NetworkStatusBar.swift -// punchnet -// -// Created by 安礼成 on 2026/4/17. -// -import SwiftUI - -struct NetworkStatusBar: View { - var model: NetworkModel - - var body: some View { - let isOnBinding = Binding( - get: { self.model.isTunnelEnabled }, - set: { newValue in - Task { @MainActor in - await self.model.setConnectionEnabled(newValue) - } - } - ) - - HStack(spacing: 12) { - // 左侧:状态指示器与文字 - HStack(spacing: 20) { - ZStack { - Circle() - .fill(self.model.isTunnelEnabled ? Color.green.opacity(0.15) : Color.primary.opacity(0.05)) - .frame(width: 36, height: 36) - - Image(systemName: self.model.isTunnelEnabled ? "checkmark.shield.fill" : "shield.slash.fill") - .symbolRenderingMode(.hierarchical) - .foregroundStyle(self.model.isTunnelEnabled ? Color.green : Color.secondary) - .font(.system(size: 16)) - } - - VStack(alignment: .leading, spacing: 1) { - if let networkSession = self.model.networkSession { - Text(networkSession.networkName) - .font(.system(size: 12, weight: .semibold)) - - Text("局域网IP: \(self.model.networkContext?.ip ?? "0.0.0.0")") - .font(.system(size: 10, design: .monospaced)) - .foregroundColor(.secondary) - } else { - Text("未登录网络") - .font(.system(size: 12, weight: .semibold)) - - Text("登录后可建立连接") - .font(.system(size: 10)) - .foregroundColor(.secondary) - } - } - } - - if self.model.networkSession != nil { - exitNodeMenu - } - - // 右侧:Switch 开关 - // 注意:这里使用 Binding 手动接管连接/断开逻辑 - Toggle("", isOn: isOnBinding) - .toggleStyle(.switch) - .controlSize(.small) // macOS 顶部栏或面板推荐使用 small 尺寸 - .disabled(self.model.phase == .connecting || self.model.phase == .disconnecting || self.model.networkSession == nil) - } - .padding(.vertical, 5) - } - - private var exitNodeMenu: some View { - Menu { - Button { - Task { @MainActor in - await self.model.updateExitNodeSelection(nil) - } - } label: { - if self.model.selectedExitNode == nil { - Label("不设置出口节点", systemImage: "checkmark") - } else { - Text("不设置出口节点") - } - } - - if !self.model.exitNodeOptions.isEmpty { - Divider() - - ForEach(self.model.exitNodeOptions) { option in - Button { - Task { @MainActor in - await self.model.updateExitNodeSelection(option.ip) - } - } label: { - if self.model.selectedExitNode?.ip == option.ip { - Label(option.nodeNameWithIp, systemImage: "checkmark") - } else { - Text(option.nodeNameWithIp) - } - } - } - } - } label: { - HStack(spacing: 10) { - VStack(alignment: .leading, spacing: 3) { - Text("出口节点") - .font(.system(size: 10, weight: .medium)) - .foregroundColor(.secondary) - - Text(self.model.exitNodeTitle) - .font(.system(size: 12, weight: .semibold)) - .foregroundColor(.primary) - .lineLimit(1) - - Text(self.model.exitNodeSubtitle) - .font(.system(size: 10, design: .monospaced)) - .foregroundColor(.secondary) - .lineLimit(1) - } - - Spacer(minLength: 0) - - if self.model.isUpdatingExitNode { - ProgressView() - .controlSize(.small) - } else { - Image(systemName: "chevron.down") - .font(.system(size: 10, weight: .semibold)) - .foregroundColor(.secondary) - } - } - .padding(.horizontal, 12) - .padding(.vertical, 8) - .frame(width: 220, alignment: .leading) - .background( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color.primary.opacity(0.04)) - ) - .overlay( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.primary.opacity(0.06), lineWidth: 1) - ) - } - .buttonStyle(.plain) - .disabled(!self.model.canSelectExitNode) - .opacity(self.model.canSelectExitNode ? 1 : 0.7) - .help(self.model.exitNodeHelpText) - } -} - -struct ExitNodeOption: Identifiable, Equatable { - let id: Int - let nodeName: String - let ip: String - let system: String? - - var nodeNameWithIp: String { - "\(nodeName) (\(ip))" - } -} diff --git a/punchnet/Features/Network/Views/NetworkView.swift b/punchnet/Features/Network/Views/NetworkView.swift index f865926..b488341 100644 --- a/punchnet/Features/Network/Views/NetworkView.swift +++ b/punchnet/Features/Network/Views/NetworkView.swift @@ -100,5 +100,141 @@ struct NetworkView: View { } } +private struct NetworkStatusBar: View { + var model: NetworkModel - + var body: some View { + let isOnBinding = Binding( + get: { self.model.isTunnelEnabled }, + set: { newValue in + Task { @MainActor in + await self.model.setConnectionEnabled(newValue) + } + } + ) + + HStack(spacing: 12) { + // 左侧:状态指示器与文字 + HStack(spacing: 20) { + ZStack { + Circle() + .fill(self.model.isTunnelEnabled ? Color.green.opacity(0.15) : Color.primary.opacity(0.05)) + .frame(width: 36, height: 36) + + Image(systemName: self.model.isTunnelEnabled ? "checkmark.shield.fill" : "shield.slash.fill") + .symbolRenderingMode(.hierarchical) + .foregroundStyle(self.model.isTunnelEnabled ? Color.green : Color.secondary) + .font(.system(size: 16)) + } + + VStack(alignment: .leading, spacing: 1) { + if let networkSession = self.model.networkSession { + Text(networkSession.networkName) + .font(.system(size: 12, weight: .semibold)) + + Text("局域网IP: \(self.model.networkContext?.ip ?? "0.0.0.0")") + .font(.system(size: 10, design: .monospaced)) + .foregroundColor(.secondary) + } else { + Text("未登录网络") + .font(.system(size: 12, weight: .semibold)) + + Text("登录后可建立连接") + .font(.system(size: 10)) + .foregroundColor(.secondary) + } + } + } + + if self.model.networkSession != nil { + exitNodeMenu + } + + // 右侧:Switch 开关 + // 注意:这里使用 Binding 手动接管连接/断开逻辑 + Toggle("", isOn: isOnBinding) + .toggleStyle(.switch) + .controlSize(.small) // macOS 顶部栏或面板推荐使用 small 尺寸 + .disabled(self.model.phase == .connecting || self.model.phase == .disconnecting || self.model.networkSession == nil) + } + .padding(.vertical, 5) + } + + private var exitNodeMenu: some View { + Menu { + Button { + Task { @MainActor in + await self.model.updateExitNodeSelection(nil) + } + } label: { + if self.model.selectedExitNode == nil { + Label("不设置出口节点", systemImage: "checkmark") + } else { + Text("不设置出口节点") + } + } + + if !self.model.exitNodeOptions.isEmpty { + Divider() + + ForEach(self.model.exitNodeOptions) { option in + Button { + Task { @MainActor in + await self.model.updateExitNodeSelection(option.ip) + } + } label: { + if self.model.selectedExitNode?.ip == option.ip { + Label(option.nodeNameWithIp, systemImage: "checkmark") + } else { + Text(option.nodeNameWithIp) + } + } + } + } + } label: { + HStack(spacing: 10) { + VStack(alignment: .leading, spacing: 3) { + Text("出口节点") + .font(.system(size: 10, weight: .medium)) + .foregroundColor(.secondary) + + Text(self.model.exitNodeTitle) + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(.primary) + .lineLimit(1) + + Text(self.model.exitNodeSubtitle) + .font(.system(size: 10, design: .monospaced)) + .foregroundColor(.secondary) + .lineLimit(1) + } + + Spacer(minLength: 0) + + if self.model.isUpdatingExitNode { + ProgressView() + .controlSize(.small) + } else { + Image(systemName: "chevron.down") + .font(.system(size: 10, weight: .semibold)) + .foregroundColor(.secondary) + } + } + .padding(.horizontal, 12) + .padding(.vertical, 8) + .frame(width: 220, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color.primary.opacity(0.04)) + ) + .overlay( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .stroke(Color.primary.opacity(0.06), lineWidth: 1) + ) + } + .buttonStyle(.plain) + .disabled(!self.model.canSelectExitNode) + .opacity(self.model.canSelectExitNode ? 1 : 0.7) + .help(self.model.exitNodeHelpText) + } +}