fix views

This commit is contained in:
anlicheng 2026-04-17 16:06:51 +08:00
parent 46baa8f9a7
commit c5b2cb3e83
4 changed files with 155 additions and 162 deletions

View File

@ -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))"
}
}
}

View File

@ -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

View File

@ -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))"
}
}

View File

@ -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)
}
}