diff --git a/V2rayU.xcodeproj/project.pbxproj b/V2rayU.xcodeproj/project.pbxproj index 082591d..a3549cc 100644 --- a/V2rayU.xcodeproj/project.pbxproj +++ b/V2rayU.xcodeproj/project.pbxproj @@ -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 = ""; }; 662CC3E72D1BED9B006E8450 /* V2rayStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2rayStruct.swift; sourceTree = ""; }; 662CC3E92D1BED9B006E8450 /* V2rayU.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = V2rayU.entitlements; sourceTree = ""; }; - 662CC3EA2D1BED9B006E8450 /* OutboundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundView.swift; sourceTree = ""; }; + 662CC3EA2D1BED9B006E8450 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = ""; }; 662CC3EB2D1BED9B006E8450 /* SecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityView.swift; sourceTree = ""; }; 662CC3EC2D1BED9B006E8450 /* StreamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamView.swift; sourceTree = ""; }; 662CC3EF2D1BED9B006E8450 /* ProfileForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileForm.swift; sourceTree = ""; }; @@ -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 */, diff --git a/V2rayU/V2ray/V2rayOutbound.swift b/V2rayU/V2ray/V2rayOutbound.swift index 8b0be10..53f9f9b 100644 --- a/V2rayU/V2ray/V2rayOutbound.swift +++ b/V2rayU/V2ray/V2rayOutbound.swift @@ -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 = "" diff --git a/V2rayU/Views/AppContentView.swift b/V2rayU/Views/AppContentView.swift index 0649082..2aa4f6a 100644 --- a/V2rayU/Views/AppContentView.swift +++ b/V2rayU/Views/AppContentView.swift @@ -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) // 设置右侧内容区的宽度 } diff --git a/V2rayU/Views/Base/Common.swift b/V2rayU/Views/Base/Common.swift index 43ab1fd..10ae3bd 100644 --- a/V2rayU/Views/Base/Common.swift +++ b/V2rayU/Views/Base/Common.swift @@ -15,12 +15,12 @@ func getTextField(name: String, text: Binding) -> 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, labelWidth: CGFloat = 120) -> some View { +func getTextFieldWithLabel(label: String, text: Binding, labelWidth: CGFloat = 100) -> some View { HStack { Text(label).frame(width: labelWidth, alignment: .trailing) Spacer() @@ -31,7 +31,7 @@ func getTextFieldWithLabel(label: String, text: Binding, labelWidth: CGF } @MainActor -func getNumFieldWithLabel(label: String, num: Binding, labelWidth: CGFloat = 120) -> some View { +func getNumFieldWithLabel(label: String, num: Binding, labelWidth: CGFloat = 100) -> some View { HStack { Text(label).frame(width: labelWidth, alignment: .trailing) Spacer() @@ -42,13 +42,13 @@ func getNumFieldWithLabel(label: String, num: Binding, labelWidth: CGFloat } @MainActor -func getPickerWithLabel(label: String, selection: Binding, labelWidth: CGFloat = 120) -> some View where T.RawValue == String, T.AllCases: RandomAccessCollection { +func getPickerWithLabel(label: String, selection: Binding,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(label: St } @MainActor -func getBoolFieldWithLabel(label: String, isOn: Binding, labelWidth: CGFloat = 120) -> some View { +func getBoolFieldWithLabel(label: String, isOn: Binding, 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, labelWidth: CGFlo @MainActor -func getTextEditorWithLabel(label: String, text: Binding, labelWidth: CGFloat = 120) -> some View { +func getTextEditorWithLabel(label: String, text: Binding, labelWidth: CGFloat = 100) -> some View { HStack { Text(label).frame(width: labelWidth, alignment: .trailing) Spacer() diff --git a/V2rayU/Views/Profile/ProfileForm.swift b/V2rayU/Views/Profile/ProfileForm.swift index 9680bda..22dff8d 100644 --- a/V2rayU/Views/Profile/ProfileForm.swift +++ b/V2rayU/Views/Profile/ProfileForm.swift @@ -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{ - ConfigServerView(item: item) - ConfigStreamView(item: item) - ConfigTransportView(item: item) + VStack { + VStack{ + ConfigServerView(item: item) + Spacer(minLength: 12) + ConfigStreamView(item: item) + Spacer(minLength: 12) + ConfigTransportView(item: item) + } + .padding(.all, 12) + .padding(.leading, 8) } - .frame(width: 400) // 左 - - Divider().frame(width: 0) // 分隔线,适当调整宽度 - - ConfigShowView(item: item) // 右 - }.padding() - Spacer() + .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)") } } } - diff --git a/V2rayU/Views/Profile/ProfileForm/OutboundView.swift b/V2rayU/Views/Profile/ProfileForm/OutboundView.swift deleted file mode 100644 index 5b7e398..0000000 --- a/V2rayU/Views/Profile/ProfileForm/OutboundView.swift +++ /dev/null @@ -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")) -} diff --git a/V2rayU/Views/Profile/ProfileForm/SecurityView.swift b/V2rayU/Views/Profile/ProfileForm/SecurityView.swift index 9d2e8a3..5c391df 100644 --- a/V2rayU/Views/Profile/ProfileForm/SecurityView.swift +++ b/V2rayU/Views/Profile/ProfileForm/SecurityView.swift @@ -6,31 +6,47 @@ // import SwiftUI - struct ConfigTransportView: View { @ObservedObject var item: ProfileModel - var body: some View{ - HStack { - VStack{ - Section(header: Text("Transport Settings")) { - getPickerWithLabel(label: "Security", selection: $item.security) - getBoolFieldWithLabel(label: "allowInsecure", isOn: $item.allowInsecure) - getTextFieldWithLabel(label: "serverName(SNI)", text: $item.sni) - 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) - } else { - getPickerWithLabel(label: "alpn", selection: $item.alpn) - } + + var body: some View { + VStack { + HStack { + Image(systemName: "lock.shield") + Text("Transport Settings") + Spacer() + } + .foregroundColor(.primary) + .padding(.horizontal, 10) + + VStack { + getPickerWithLabel(label: "Security", selection: $item.security) + getBoolFieldWithLabel(label: "Allow Insecure", isOn: $item.allowInsecure) + getTextFieldWithLabel(label: "serverName(SNI)", text: $item.sni) + getPickerWithLabel(label: "Fingerprint", selection: $item.fingerprint) + if item.security == .reality { + 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) } } + .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")) } diff --git a/V2rayU/Views/Profile/ProfileForm/ServerView.swift b/V2rayU/Views/Profile/ProfileForm/ServerView.swift new file mode 100644 index 0000000..19e458f --- /dev/null +++ b/V2rayU/Views/Profile/ProfileForm/ServerView.swift @@ -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")) +} diff --git a/V2rayU/Views/Profile/ProfileForm/StreamView.swift b/V2rayU/Views/Profile/ProfileForm/StreamView.swift index 3aa12a5..14ac119 100644 --- a/V2rayU/Views/Profile/ProfileForm/StreamView.swift +++ b/V2rayU/Views/Profile/ProfileForm/StreamView.swift @@ -9,45 +9,79 @@ import SwiftUI struct ConfigStreamView: View { @ObservedObject var item: ProfileModel - + var body: some View { - HStack { + VStack { + HStack { + Image(systemName: "waveform.path") + Text("Stream Settings") + Spacer() + } + .foregroundColor(.primary) + .padding(.horizontal, 10) + VStack { - Section(header: Text("Stream Settings")) { - 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) - } - getTextFieldWithLabel(label: "request host",text: $item.host) - getTextFieldWithLabel(label: "request path",text: $item.path) + getPickerWithLabel(label: "Network", selection: $item.network) + if item.network == .tcp { + if item.network == .tcp { + getPickerWithLabel(label: "Header Type", selection: $item.headerType) } - if item.network == .grpc { - getTextFieldWithLabel(label: "serviceName",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) - } - if item.network == .domainsocket { - 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) - } - if item.network == .xhttp { - getTextFieldWithLabel(label: "path",text: $item.path) - getTextFieldWithLabel(label: "host",text: $item.host) + 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: "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) + } + + if item.network == .domainsocket { + 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: "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) + } } + .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)) + ) + ) } } } diff --git a/V2rayU/Views/Profile/ProfileListView.swift b/V2rayU/Views/Profile/ProfileListView.swift index 7d6077f..b651e56 100644 --- a/V2rayU/Views/Profile/ProfileListView.swift +++ b/V2rayU/Views/Profile/ProfileListView.swift @@ -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) diff --git a/V2rayU/Views/Profile/ProfileShow.swift b/V2rayU/Views/Profile/ProfileShow.swift index 566582d..05234f1 100644 --- a/V2rayU/Views/Profile/ProfileShow.swift +++ b/V2rayU/Views/Profile/ProfileShow.swift @@ -12,12 +12,27 @@ struct ConfigShowView: View { var body: some View { HStack { VStack { - Section(header: Text("Outbound Preview")) { - JSONTextView(jsonString: V2rayOutboundHandler(from: item).toJSON()) + HStack { + Image(systemName: "waveform.path") + Text("Outbound Preview") + Spacer() } - 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() } } } diff --git a/V2rayU/Views/Subscription/SubscriptionList.swift b/V2rayU/Views/Subscription/SubscriptionList.swift index 59d4276..d323fd0 100644 --- a/V2rayU/Views/Subscription/SubscriptionList.swift +++ b/V2rayU/Views/Subscription/SubscriptionList.swift @@ -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))