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

View File

@ -12,8 +12,17 @@ protocol V2rayOutboundSettings: Codable {}
// MARK: - Protocol Definitions // MARK: - Protocol Definitions
enum V2rayProtocolOutbound: String, Codable, CaseIterable, Identifiable { 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 } var id: Self { self }
func showInEditor () -> Bool {
switch self {
case .socks, .dns, .http, .blackhole, .freedom:
return false
default:
return true
}
}
} }
// MARK: - V2rayOutbound Definition // MARK: - V2rayOutbound Definition
@ -123,7 +132,7 @@ struct V2rayOutboundShadowsocks: V2rayOutboundSettings {
var servers: [V2rayOutboundShadowsockServer] = [V2rayOutboundShadowsockServer()] 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 { struct V2rayOutboundShadowsockServer: Codable {
var address: String = "" var address: String = ""

View File

@ -62,8 +62,16 @@ struct ContentView: View {
} }
.background() // .background() //
.padding(.all, 16) // .padding(.all, 16) //
.background() // .background(
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) // 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) // .padding(.all, 16) //
.frame(width: 800) // .frame(width: 800) //
} }

View File

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

View File

@ -20,9 +20,9 @@ struct ConfigFormView: View {
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text("Subscription Settings") Text("Profile Settings")
.font(.headline) .font(.headline)
Text("Edit your Profile information") Text("Edit your proxy configuration")
.font(.subheadline) .font(.subheadline)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
@ -32,24 +32,32 @@ struct ConfigFormView: View {
.padding(.leading, 24) .padding(.leading, 24)
Divider() Divider()
HStack { HStack {
VStack{ VStack {
ConfigServerView(item: item) VStack{
ConfigStreamView(item: item) ConfigServerView(item: item)
ConfigTransportView(item: item) Spacer(minLength: 12)
ConfigStreamView(item: item)
Spacer(minLength: 12)
ConfigTransportView(item: item)
}
.padding(.all, 12)
.padding(.leading, 8)
} }
.frame(width: 400) // .frame(width: 400)
Divider()
Divider().frame(width: 0) // 线 VStack{
ConfigShowView(item: item)
ConfigShowView(item: item) // .padding(.all, 12)
}.padding() .padding(.trailing, 8)
Spacer() }
}
Divider() Divider()
HStack { HStack {
Spacer() Spacer()
Button("Cancel") { Button("Cancel") {
onClose() onClose()
} }
.buttonStyle(.bordered)
Button("Save") { Button("Save") {
viewModel.upsert(item: item) viewModel.upsert(item: item)
onClose() onClose()
@ -59,10 +67,14 @@ struct ConfigFormView: View {
.padding(.vertical, 12) .padding(.vertical, 12)
.padding(.horizontal, 24) .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 { .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 import SwiftUI
struct ConfigTransportView: View { struct ConfigTransportView: View {
@ObservedObject var item: ProfileModel @ObservedObject var item: ProfileModel
var body: some View{
HStack { var body: some View {
VStack{ VStack {
Section(header: Text("Transport Settings")) { HStack {
getPickerWithLabel(label: "Security", selection: $item.security) Image(systemName: "lock.shield")
getBoolFieldWithLabel(label: "allowInsecure", isOn: $item.allowInsecure) Text("Transport Settings")
getTextFieldWithLabel(label: "serverName(SNI)", text: $item.sni) Spacer()
getPickerWithLabel(label: "fingerprint", selection: $item.fingerprint) }
if item.security == .reality { .foregroundColor(.primary)
getTextFieldWithLabel(label: "PublicKey", text: $item.publicKey) .padding(.horizontal, 10)
getTextFieldWithLabel(label: "ShortId", text: $item.shortId)
getTextFieldWithLabel(label: "spiderX", text: $item.spiderX) VStack {
} else { getPickerWithLabel(label: "Security", selection: $item.security)
getPickerWithLabel(label: "alpn", selection: $item.alpn) 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 { #Preview {
ConfigTransportView(item: ProfileModel(remark: "test01", protocol: .trojan, address: "dss", port: 443, password: "aaa", encryption: "auto")) 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

@ -9,45 +9,79 @@ import SwiftUI
struct ConfigStreamView: View { struct ConfigStreamView: View {
@ObservedObject var item: ProfileModel @ObservedObject var item: ProfileModel
var body: some View { var body: some View {
HStack { VStack {
HStack {
Image(systemName: "waveform.path")
Text("Stream Settings")
Spacer()
}
.foregroundColor(.primary)
.padding(.horizontal, 10)
VStack { VStack {
Section(header: Text("Stream Settings")) { getPickerWithLabel(label: "Network", selection: $item.network)
getPickerWithLabel(label: "Network", selection: $item.network) if item.network == .tcp {
if item.network == .tcp || item.network == .ws || item.network == .h2 { if item.network == .tcp {
if item.network == .tcp || item.network == .h2 { getPickerWithLabel(label: "Header Type", selection: $item.headerType)
getPickerWithLabel(label: "header type", selection: $item.headerType)
}
getTextFieldWithLabel(label: "request host",text: $item.host)
getTextFieldWithLabel(label: "request path",text: $item.path)
} }
if item.network == .grpc { if item.headerType == .http {
getTextFieldWithLabel(label: "serviceName",text: $item.path) getTextFieldWithLabel(label: "Request Host", text: $item.host)
} getTextFieldWithLabel(label: "Request Path", 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.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))
)
)
} }
} }
} }

View File

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

View File

@ -12,12 +12,27 @@ struct ConfigShowView: View {
var body: some View { var body: some View {
HStack { HStack {
VStack { VStack {
Section(header: Text("Outbound Preview")) { HStack {
JSONTextView(jsonString: V2rayOutboundHandler(from: item).toJSON()) 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()
} }
} }
} }

View File

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