优化profile ui

This commit is contained in:
yanue 2025-06-20 23:19:51 +08:00
parent dadb70a85e
commit ffcd846bae
12 changed files with 295 additions and 172 deletions

View File

@ -15,7 +15,7 @@
662CC4032D1BED9B006E8450 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3C52D1BED9B006E8450 /* String+Extension.swift */; };
662CC4052D1BED9B006E8450 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3C42D1BED9B006E8450 /* Shell.swift */; };
662CC4062D1BED9B006E8450 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3C72D1BED9B006E8450 /* UserDefaults.swift */; };
662CC4072D1BED9B006E8450 /* OutboundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3EA2D1BED9B006E8450 /* OutboundView.swift */; };
662CC4072D1BED9B006E8450 /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3EA2D1BED9B006E8450 /* ServerView.swift */; };
662CC4082D1BED9B006E8450 /* SecurityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3EB2D1BED9B006E8450 /* SecurityView.swift */; };
662CC40A2D1BED9B006E8450 /* V2rayLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3E12D1BED9B006E8450 /* V2rayLaunch.swift */; };
662CC40B2D1BED9B006E8450 /* StreamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662CC3EC2D1BED9B006E8450 /* StreamView.swift */; };
@ -167,7 +167,7 @@
662CC3E62D1BED9B006E8450 /* V2rayStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2rayStream.swift; sourceTree = "<group>"; };
662CC3E72D1BED9B006E8450 /* V2rayStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2rayStruct.swift; sourceTree = "<group>"; };
662CC3E92D1BED9B006E8450 /* V2rayU.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = V2rayU.entitlements; sourceTree = "<group>"; };
662CC3EA2D1BED9B006E8450 /* OutboundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundView.swift; sourceTree = "<group>"; };
662CC3EA2D1BED9B006E8450 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = "<group>"; };
662CC3EB2D1BED9B006E8450 /* SecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityView.swift; sourceTree = "<group>"; };
662CC3EC2D1BED9B006E8450 /* StreamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamView.swift; sourceTree = "<group>"; };
662CC3EF2D1BED9B006E8450 /* ProfileForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileForm.swift; sourceTree = "<group>"; };
@ -376,7 +376,7 @@
662CC3ED2D1BED9B006E8450 /* ProfileForm */ = {
isa = PBXGroup;
children = (
662CC3EA2D1BED9B006E8450 /* OutboundView.swift */,
662CC3EA2D1BED9B006E8450 /* ServerView.swift */,
662CC3EB2D1BED9B006E8450 /* SecurityView.swift */,
662CC3EC2D1BED9B006E8450 /* StreamView.swift */,
);
@ -746,7 +746,7 @@
66B1B5312D282DAB0032DD09 /* V2rayMetrics.swift in Sources */,
662CC4062D1BED9B006E8450 /* UserDefaults.swift in Sources */,
663745372D2EC8840093A101 /* MenuSpeed.swift in Sources */,
662CC4072D1BED9B006E8450 /* OutboundView.swift in Sources */,
662CC4072D1BED9B006E8450 /* ServerView.swift in Sources */,
663814B52E0448DB00F5FCF3 /* ProfilePing.swift in Sources */,
662CC4082D1BED9B006E8450 /* SecurityView.swift in Sources */,
662CC40A2D1BED9B006E8450 /* V2rayLaunch.swift in Sources */,

View File

@ -12,8 +12,17 @@ protocol V2rayOutboundSettings: Codable {}
// MARK: - Protocol Definitions
enum V2rayProtocolOutbound: String, Codable, CaseIterable, Identifiable {
case vmess, vless, trojan, shadowsocks, socks, dns, http, blackhole, freedom
case trojan, vmess, vless, shadowsocks, socks, dns, http, blackhole, freedom
var id: Self { self }
func showInEditor () -> Bool {
switch self {
case .socks, .dns, .http, .blackhole, .freedom:
return false
default:
return true
}
}
}
// MARK: - V2rayOutbound Definition
@ -123,7 +132,7 @@ struct V2rayOutboundShadowsocks: V2rayOutboundSettings {
var servers: [V2rayOutboundShadowsockServer] = [V2rayOutboundShadowsockServer()]
}
let V2rayOutboundShadowsockMethod = ["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "chacha20-ietf-poly1305", "chacha20-poly1305", "aes-128-gcm", "aes-256-gcm", "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-192-gcm", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "bf-cfb", "salsa20", "chacha20", "chacha20-ietf"]
let V2rayOutboundShadowsockMethod = ["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "none", "plain"]
struct V2rayOutboundShadowsockServer: Codable {
var address: String = ""

View File

@ -62,8 +62,16 @@ struct ContentView: View {
}
.background() //
.padding(.all, 16) //
.background() //
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) //
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(NSColor.alternateSelectedControlTextColor))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black.opacity(0.2), lineWidth: 1)
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.clipShape(RoundedRectangle(cornerRadius: 10))
)
)
.padding(.all, 16) //
.frame(width: 800) //
}

View File

@ -15,12 +15,12 @@ func getTextField(name: String, text: Binding<String>) -> some View {
}
// getTextField name
func getTextLabel(label: String, labelWidth: CGFloat = 120) -> some View {
func getTextLabel(label: String, labelWidth: CGFloat = 100) -> some View {
Text(label).frame(width: labelWidth, alignment: .trailing)
}
@MainActor
func getTextFieldWithLabel(label: String, text: Binding<String>, labelWidth: CGFloat = 120) -> some View {
func getTextFieldWithLabel(label: String, text: Binding<String>, labelWidth: CGFloat = 100) -> some View {
HStack {
Text(label).frame(width: labelWidth, alignment: .trailing)
Spacer()
@ -31,7 +31,7 @@ func getTextFieldWithLabel(label: String, text: Binding<String>, labelWidth: CGF
}
@MainActor
func getNumFieldWithLabel(label: String, num: Binding<Int>, labelWidth: CGFloat = 120) -> some View {
func getNumFieldWithLabel(label: String, num: Binding<Int>, labelWidth: CGFloat = 100) -> some View {
HStack {
Text(label).frame(width: labelWidth, alignment: .trailing)
Spacer()
@ -42,13 +42,13 @@ func getNumFieldWithLabel(label: String, num: Binding<Int>, labelWidth: CGFloat
}
@MainActor
func getPickerWithLabel<T: CaseIterable & RawRepresentable & Hashable>(label: String, selection: Binding<T>, labelWidth: CGFloat = 120) -> some View where T.RawValue == String, T.AllCases: RandomAccessCollection {
func getPickerWithLabel<T: CaseIterable & RawRepresentable & Hashable>(label: String, selection: Binding<T>,ignore: [T] = [], labelWidth: CGFloat = 100) -> some View where T.RawValue == String, T.AllCases: RandomAccessCollection {
HStack {
Text(label)
.frame(width: labelWidth, alignment: .trailing)
Spacer()
Picker("", selection: selection) {
ForEach(T.allCases, id: \.self) { pick in
ForEach(T.allCases.filter { !ignore.contains($0) }, id: \.self) { pick in
Text(pick.rawValue)
}
}
@ -57,7 +57,7 @@ func getPickerWithLabel<T: CaseIterable & RawRepresentable & Hashable>(label: St
}
@MainActor
func getBoolFieldWithLabel(label: String, isOn: Binding<Bool>, labelWidth: CGFloat = 120) -> some View {
func getBoolFieldWithLabel(label: String, isOn: Binding<Bool>, labelWidth: CGFloat = 100) -> some View {
HStack {
Text(label).frame(width: labelWidth, alignment: .trailing)
Toggle("", isOn: isOn).frame(alignment: .leading)
@ -69,7 +69,7 @@ func getBoolFieldWithLabel(label: String, isOn: Binding<Bool>, labelWidth: CGFlo
@MainActor
func getTextEditorWithLabel(label: String, text: Binding<String>, labelWidth: CGFloat = 120) -> some View {
func getTextEditorWithLabel(label: String, text: Binding<String>, labelWidth: CGFloat = 100) -> some View {
HStack {
Text(label).frame(width: labelWidth, alignment: .trailing)
Spacer()

View File

@ -20,9 +20,9 @@ struct ConfigFormView: View {
.frame(width: 32, height: 32)
.foregroundColor(.accentColor)
VStack(alignment: .leading, spacing: 2) {
Text("Subscription Settings")
Text("Profile Settings")
.font(.headline)
Text("Edit your Profile information")
Text("Edit your proxy configuration")
.font(.subheadline)
.foregroundColor(.secondary)
}
@ -32,24 +32,32 @@ struct ConfigFormView: View {
.padding(.leading, 24)
Divider()
HStack {
VStack {
VStack{
ConfigServerView(item: item)
Spacer(minLength: 12)
ConfigStreamView(item: item)
Spacer(minLength: 12)
ConfigTransportView(item: item)
}
.frame(width: 400) //
Divider().frame(width: 0) // 线
ConfigShowView(item: item) //
}.padding()
Spacer()
.padding(.all, 12)
.padding(.leading, 8)
}
.frame(width: 400)
Divider()
VStack{
ConfigShowView(item: item)
.padding(.all, 12)
.padding(.trailing, 8)
}
}
Divider()
HStack {
Spacer()
Button("Cancel") {
onClose()
}
.buttonStyle(.bordered)
Button("Save") {
viewModel.upsert(item: item)
onClose()
@ -59,10 +67,14 @@ struct ConfigFormView: View {
.padding(.vertical, 12)
.padding(.horizontal, 24)
}
.frame(width: 660)
.frame(width: 760)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(NSColor.windowBackgroundColor))
.shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 2)
)
.onAppear {
print("ConfigView appeared with item: \(item.id)")
print("ProfileFormView appeared with item: \(item.id)")
}
}
}

View File

@ -1,80 +0,0 @@
//
// ServerView.swift
// V2rayU
//
// Created by yanue on 2024/11/24.
//
import SwiftUI
struct ConfigServerView: View {
@ObservedObject var item: ProfileModel
var body: some View {
VStack {
getTextFieldWithLabel(label: "Remark", text: $item.remark)
}
VStack {
VStack {
Section(header: Text("Server Settings")) {
getPickerWithLabel(label: "Protocol", selection: $item.protocol)
if item.protocol == .trojan {
getTextFieldWithLabel(label: "address", text: $item.address)
getNumFieldWithLabel(label: "port", num: $item.port)
getTextFieldWithLabel(label: "password", text: $item.password)
}
if item.protocol == .vmess {
getTextFieldWithLabel(label: "address", text: $item.address)
getNumFieldWithLabel(label: "port", num: $item.port)
getTextFieldWithLabel(label: "id", text: $item.password)
getNumFieldWithLabel(label: "alterId", num: $item.alterId)
HStack {
getTextLabel(label: "encryption")
Spacer()
Picker("", selection: $item.encryption) {
ForEach(V2rayProtocolOutbound.allCases) { pick in
Text(pick.rawValue)
}
}
}
}
if item.protocol == .vless {
getTextFieldWithLabel(label: "address", text: $item.address)
getNumFieldWithLabel(label: "port", num: $item.port)
getTextFieldWithLabel(label: "id", text: $item.password)
getTextFieldWithLabel(label: "flow", text: $item.flow)
HStack {
getTextLabel(label: "encryption")
Spacer()
Picker("", selection: $item.encryption) {
ForEach(V2rayProtocolOutbound.allCases) { pick in
Text(pick.rawValue)
}
}
}
}
}
if item.protocol == .shadowsocks {
getTextFieldWithLabel(label: "address", text: $item.address)
getNumFieldWithLabel(label: "port", num: $item.port)
getTextFieldWithLabel(label: "password", text: $item.password)
HStack {
getTextLabel(label: "method")
Picker("", selection: $item.encryption) {
ForEach(V2rayProtocolOutbound.allCases) { pick in
Text(pick.rawValue)
}
}
}
}
}
}
}
}
#Preview {
ConfigServerView(item: ProfileModel(remark: "test01", protocol: .trojan, address: "dss", port: 443, password: "aaa", encryption: "auto"))
}

View File

@ -6,31 +6,47 @@
//
import SwiftUI
struct ConfigTransportView: View {
@ObservedObject var item: ProfileModel
var body: some View{
var body: some View {
VStack {
HStack {
VStack{
Section(header: Text("Transport Settings")) {
Image(systemName: "lock.shield")
Text("Transport Settings")
Spacer()
}
.foregroundColor(.primary)
.padding(.horizontal, 10)
VStack {
getPickerWithLabel(label: "Security", selection: $item.security)
getBoolFieldWithLabel(label: "allowInsecure", isOn: $item.allowInsecure)
getBoolFieldWithLabel(label: "Allow Insecure", isOn: $item.allowInsecure)
getTextFieldWithLabel(label: "serverName(SNI)", text: $item.sni)
getPickerWithLabel(label: "fingerprint", selection: $item.fingerprint)
getPickerWithLabel(label: "Fingerprint", selection: $item.fingerprint)
if item.security == .reality {
getTextFieldWithLabel(label: "PublicKey", text: $item.publicKey)
getTextFieldWithLabel(label: "ShortId", text: $item.shortId)
getTextFieldWithLabel(label: "spiderX", text: $item.spiderX)
getTextFieldWithLabel(label: "Public Key", text: $item.publicKey)
getTextFieldWithLabel(label: "Short ID", text: $item.shortId)
getTextFieldWithLabel(label: "SpiderX", text: $item.spiderX)
} else {
getPickerWithLabel(label: "alpn", selection: $item.alpn)
}
getPickerWithLabel(label: "ALPN", selection: $item.alpn)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(NSColor.alternateSelectedControlTextColor))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black.opacity(0.2), lineWidth: 1)
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.clipShape(RoundedRectangle(cornerRadius: 10))
)
)
}
}
}
#Preview {
ConfigTransportView(item: ProfileModel(remark: "test01", protocol: .trojan, address: "dss", port: 443, password: "aaa", encryption: "auto"))
}

View File

@ -0,0 +1,82 @@
//
// ServerView.swift
// V2rayU
//
// Created by yanue on 2024/11/24.
//
import SwiftUI
struct ConfigServerView: View {
@ObservedObject var item: ProfileModel
var body: some View {
VStack {
HStack {
Image(systemName: "server.rack")
Text("Server Settings")
Spacer()
}
.foregroundColor(.primary)
.padding(.horizontal, 10)
VStack {
getTextFieldWithLabel(label: "Remark", text: $item.remark)
// : socks, dns, http, blackhole, freedom
getPickerWithLabel(label: "Protocol", selection: $item.protocol,ignore: [.socks, .dns, .http, .blackhole, .freedom])
if item.protocol == .trojan {
getTextFieldWithLabel(label: "Address", text: $item.address)
getNumFieldWithLabel(label: "Port", num: $item.port)
getTextFieldWithLabel(label: "Password", text: $item.password)
}
if item.protocol == .vmess {
getTextFieldWithLabel(label: "Address", text: $item.address)
getNumFieldWithLabel(label: "Port", num: $item.port)
getTextFieldWithLabel(label: "ID", text: $item.password)
getNumFieldWithLabel(label: "Alter ID", num: $item.alterId)
getTextFieldWithLabel(label: "Encryption", text: $item.encryption)
}
if item.protocol == .vless {
getTextFieldWithLabel(label: "Address", text: $item.address)
getNumFieldWithLabel(label: "Port", num: $item.port)
getTextFieldWithLabel(label: "ID", text: $item.password)
getTextFieldWithLabel(label: "Flow", text: $item.flow)
getTextFieldWithLabel(label: "Encryption", text: $item.encryption)
}
if item.protocol == .shadowsocks {
getTextFieldWithLabel(label: "Address", text: $item.address)
getNumFieldWithLabel(label: "Port", num: $item.port)
getTextFieldWithLabel(label: "Password", text: $item.password)
HStack {
getTextLabel(label: "Method")
Picker("", selection: $item.encryption) {
ForEach(V2rayOutboundShadowsockMethod, id: \.self) { pick in
Text(pick)
}
}
}
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(NSColor.alternateSelectedControlTextColor))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black.opacity(0.2), lineWidth: 1)
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.clipShape(RoundedRectangle(cornerRadius: 10))
)
)
}
}
}
#Preview {
ConfigServerView(item: ProfileModel(remark: "test01", protocol: .trojan, address: "dss", port: 443, password: "aaa", encryption: "auto"))
}

View File

@ -11,43 +11,77 @@ struct ConfigStreamView: View {
@ObservedObject var item: ProfileModel
var body: some View {
HStack {
VStack {
Section(header: Text("Stream Settings")) {
HStack {
Image(systemName: "waveform.path")
Text("Stream Settings")
Spacer()
}
.foregroundColor(.primary)
.padding(.horizontal, 10)
VStack {
getPickerWithLabel(label: "Network", selection: $item.network)
if item.network == .tcp || item.network == .ws || item.network == .h2 {
if item.network == .tcp || item.network == .h2 {
getPickerWithLabel(label: "header type", selection: $item.headerType)
if item.network == .tcp {
if item.network == .tcp {
getPickerWithLabel(label: "Header Type", selection: $item.headerType)
}
getTextFieldWithLabel(label: "request host",text: $item.host)
getTextFieldWithLabel(label: "request path",text: $item.path)
if item.headerType == .http {
getTextFieldWithLabel(label: "Request Host", text: $item.host)
getTextFieldWithLabel(label: "Request Path", text: $item.path)
}
}
if item.network == .ws {
getTextFieldWithLabel(label: "Request Host", text: $item.host)
getTextFieldWithLabel(label: "Request Path", text: $item.path)
}
if item.network == .h2 {
getTextFieldWithLabel(label: "Request Host", text: $item.host)
getTextFieldWithLabel(label: "Request Path", text: $item.path)
}
if item.network == .grpc {
getTextFieldWithLabel(label: "serviceName",text: $item.path)
getTextFieldWithLabel(label: "Service Name", text: $item.path)
}
if item.network == .quic {
getTextFieldWithLabel(label: "Key",text: $item.path)
getPickerWithLabel(label: "header type", selection: $item.headerType)
getPickerWithLabel(label: "security", selection: $item.headerType)
getTextFieldWithLabel(label: "Key", text: $item.path)
getPickerWithLabel(label: "Header Type", selection: $item.headerType)
getPickerWithLabel(label: "Security", selection: $item.headerType)
}
if item.network == .domainsocket {
getTextFieldWithLabel(label: "path",text: $item.path)
getTextFieldWithLabel(label: "Path", text: $item.path)
}
if item.network == .kcp {
getTextFieldWithLabel(label: "seed",text: $item.path)
getPickerWithLabel(label: "header-type", selection: $item.headerType)
getBoolFieldWithLabel(label: "congestion", isOn: $item.allowInsecure)
getNumFieldWithLabel(label: "mtu", num: $item.port)
getNumFieldWithLabel(label: "tti", num: $item.port)
getNumFieldWithLabel(label: "uplinkCapacity", num: $item.port)
getNumFieldWithLabel(label: "downlinkCapacity", num: $item.port)
getTextFieldWithLabel(label: "Seed", text: $item.path)
getPickerWithLabel(label: "Header Type", selection: $item.headerType)
getBoolFieldWithLabel(label: "Congestion", isOn: $item.allowInsecure)
getNumFieldWithLabel(label: "MTU", num: $item.port)
getNumFieldWithLabel(label: "TTI", num: $item.port)
getNumFieldWithLabel(label: "Uplink Capacity", num: $item.port)
getNumFieldWithLabel(label: "Downlink Capacity", num: $item.port)
}
if item.network == .xhttp {
getTextFieldWithLabel(label: "path",text: $item.path)
getTextFieldWithLabel(label: "host",text: $item.host)
}
getTextFieldWithLabel(label: "Path", text: $item.path)
getTextFieldWithLabel(label: "Host", text: $item.host)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(NSColor.alternateSelectedControlTextColor))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black.opacity(0.2), lineWidth: 1)
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.clipShape(RoundedRectangle(cornerRadius: 10))
)
)
}
}
}

View File

@ -63,11 +63,9 @@ struct ProfileListView: View {
TextField("Search by Address or Remark", text: $searchText)
.frame(width: 200)
}
Button(action: { loadData() }) {
Label("刷新", systemImage: "arrow.clockwise")
}
}
.padding(.vertical, 6)
Spacer()
VStack {
Spacer()
HStack {
@ -100,6 +98,13 @@ struct ProfileListView: View {
Label("PingAll", systemImage: "lasso.badge.sparkles")
}
.buttonStyle(.borderedProminent)
.disabled(viewModel.list.isEmpty)
//
Button(action: {
}) {
Label("分享", systemImage: "square.and.arrow.up")
}
}.padding(.horizontal, 10)
//
Table(of: ProfileModel.self, selection: $selection, sortOrder: $sortOrder) {
@ -195,13 +200,35 @@ struct ProfileListView: View {
private func contextMenuProvider(item: ProfileModel) -> some View {
Group {
Button("Edit") {
self.selectedRow = item
}
Divider()
Button("Choose") {
}
Button("Duplicate") {
}
Button("Copy") {
}
Button("Ping") {
self.pingRow = item
}
Button("Share") {
self.pingRow = item
}
Divider()
Button("🔝 Move to Top") {
self.pingRow = item
}
Button("Move to Bottom") {
self.pingRow = item
}
Button("↑ Move Up") {
self.pingRow = item
}
Button("↓ Move Down") {
}
Divider()
Button("Edit") {
self.selectedRow = item
}
Button("Delete") {
// Handle another action
print("item.uuid", item.id, item.uuid)

View File

@ -12,12 +12,27 @@ struct ConfigShowView: View {
var body: some View {
HStack {
VStack {
Section(header: Text("Outbound Preview")) {
HStack {
Image(systemName: "waveform.path")
Text("Outbound Preview")
Spacer()
}
.foregroundColor(.primary)
.padding(.horizontal, 10)
JSONTextView(jsonString: V2rayOutboundHandler(from: item).toJSON())
.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(NSColor.alternateSelectedControlTextColor))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black.opacity(0.2), lineWidth: 1)
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.clipShape(RoundedRectangle(cornerRadius: 10))
)
)
}
Spacer()
}
Spacer()
}
}
}

View File

@ -90,7 +90,7 @@ struct SubscriptionListView: View {
Text(row.url)
.font(.system(size: 13))
.onTapGesture(count: 2) { selectedRow = row }
}.width(300)
}.width(200)
TableColumn("Port") { row in
Text("\(row.enable)")
.font(.system(size: 13))