Files
UUVPN/iOS-SwiftUI-Code/ApplicationLibrary/Views/Groups/GroupItemView.swift
T
2025-01-22 14:09:10 +08:00

281 lines
9.5 KiB
Swift

import Libbox
import Network
import Library
import SwiftUI
@MainActor
public struct GroupItemView: View {
private let _group: Binding<OutboundGroup>
private var group: OutboundGroup {
_group.wrappedValue
}
private let item: OutboundGroupItem
public init(_ group: Binding<OutboundGroup>, _ item: OutboundGroupItem) {
_group = group
self.item = item
}
@State private var alert: Alert?
/* old style
public var body: some View {
Button {
if group.selectable, group.selected != item.tag {
Task {
await selectOutbound()
UserDefaults.standard.setValue("\(item.tag)", forKey: "selectedNode")
}
}
} label: {
HStack {
VStack {
HStack {
Text(item.tag)
.font(.subheadline)
.foregroundStyle(.foreground)
.lineLimit(1)
.truncationMode(.tail)
Spacer(minLength: 0)
}
Spacer(minLength: 8)
HStack {
Text(item.displayType)
.font(.caption)
.foregroundColor(.secondary)
Spacer(minLength: 0)
}
}
Spacer(minLength: 0)
VStack {
if group.selected == item.tag {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Color.accentColor)
}
Spacer(minLength: 0)
if item.urlTestDelay > 0 {
Text(item.delayString)
.font(.caption)
.foregroundColor(item.delayColor)
}
}
}
}
#if !os(tvOS)
.buttonStyle(.borderless)
.padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13))
.background(backgroundColor)
.cornerRadius(10)
#endif
.alertBinding($alert)
}
*/
func testPing(to server: String, port: Int, completion: @escaping (TimeInterval?) -> Void) {
let startTime = Date()
let connection = NWConnection(host: NWEndpoint.Host(server), port: NWEndpoint.Port(rawValue: UInt16(port))!, using: .tcp)
connection.stateUpdateHandler = { state in
switch state {
case .ready:
// 连接成功,计算响应时间
let pingTime = Date().timeIntervalSince(startTime)
completion(pingTime)
connection.cancel() // 连接完成后取消
case .failed(_):
completion(nil)
connection.cancel()
default:
break
}
}
// 启动连接
connection.start(queue: .global())
}
public var body: some View {
Button {
print("\(group.selectable) group.selected - item.tag: >>> "+group.selected + " " + item.tag)
// print(item.toString)
if group.selectable, group.selected != item.tag {
// var item = _group.wrappedValue.items[index] // Make a mutable copy of the item
Task {
if let server = item.server ,let port = item.server_port{
//说明是第一次查看节点列表 然后 点击列表
testPing(to: server, port: port) { pingTime in
if let ping = pingTime {
print("Ping 时间: \(ping) 秒")
Task{
await pingOutbound(tag: item.tag,pingtime: UInt16((pingTime ?? 0)*1000))
}
} else {
print("连接超时")
}
}
UserDefaults.standard.setValue("\(item.tag)", forKey: "selectedNode")
await selectOutbound()
}else{
await selectOutbound()
UserDefaults.standard.setValue("\(item.tag)", forKey: "selectedNode")
//切换节点后重启服务
await restartVPNServer()
}
}
}
} label: {
VStack(spacing: 2) {
HStack {
VStack(alignment: .leading, spacing: 8) {
HStack {
// Image(server.name)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 20, height: 20)
Text(item.tag)
.font(.subheadline)
.fontWeight(.semibold)
.foregroundColor(.white)
}
Label {
Text(item.type)
// Text(item.urlTestDelay <= 3000 ? "在线可用" : "不可用")
} icon: {
// Image(systemName: item.urlTestDelay <= 3000 ? "checkmark" : "xmark")
} .foregroundColor(.gray)
// .foregroundColor(item.urlTestDelay <= 3000 ?.green : .red)
.font(.caption2)
}
Spacer(minLength: 10)
// Change Server Button...
VStack {
if group.selected == item.tag {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Color.accentColor)
}
Spacer(minLength: 0)
if item.urlTestDelay > 0 {
Text(item.delayString)
.font(.caption)
.foregroundColor(item.delayColor)
}else{
//正在测速
Text("...")
.font(.caption)
.foregroundColor(.gray)
}
}
}
.frame(height: 50)
.padding(.horizontal)
Divider()
}
}
#if !os(tvOS)
.buttonStyle(.borderless)
// .padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13))
// .background(backgroundColor)
// .cornerRadius(10)
#endif
.alertBinding($alert)
}
private nonisolated func pingOutbound(tag: String,pingtime: UInt16) async {
let newGroup = await group
let newitems = newGroup.items.map { item in
var mutableItem = item
if (item.tag == tag) {
mutableItem.urlTestDelay = pingtime
}
return mutableItem
}
let newOut = OutboundGroup(tag: newGroup.tag, type: newGroup.type, selected:tag, selectable: newGroup.selectable, isExpand: newGroup.isExpand, items: newitems)
await MainActor.run { [newOut] in
_group.wrappedValue = newOut
}
}
private nonisolated func selectOutbound() async {
do {
try await LibboxNewStandaloneCommandClient()!.selectOutbound(group.tag, outboundTag: item.tag)
var newGroup = await group
newGroup.selected = item.tag
await MainActor.run { [newGroup] in
_group.wrappedValue = newGroup
}
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
private nonisolated func setSystemProxyEnabled(_ isEnabled: Bool) async {
do {
try LibboxNewStandaloneCommandClient()!.setSystemProxyEnabled(isEnabled)
await SharedPreferences.systemProxyEnabled.set(isEnabled)
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
private nonisolated func restartVPNServer() async {
do {
try LibboxNewStandaloneCommandClient()!.serviceReload()
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
private var backgroundColor: Color {
#if os(iOS)
return Color(uiColor: .secondarySystemGroupedBackground)
#elseif os(macOS)
return Color(nsColor: .textBackgroundColor)
#elseif os(tvOS)
return Color.black
#endif
}
}