mirror of
https://github.com/yanue/V2rayU.git
synced 2025-06-27 05:30:09 +00:00
优化profile ui
This commit is contained in:
parent
dadb70a85e
commit
ffcd846bae
@ -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 */,
|
||||
|
@ -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 = ""
|
||||
|
@ -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) // 设置右侧内容区的宽度
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"))
|
||||
}
|
@ -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"))
|
||||
}
|
||||
|
82
V2rayU/Views/Profile/ProfileForm/ServerView.swift
Normal file
82
V2rayU/Views/Profile/ProfileForm/ServerView.swift
Normal 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"))
|
||||
}
|
@ -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))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user