// // ContentView.swift // sdlan // // Created by 安礼成 on 2024/1/17. // import SwiftUI import SwiftData import Combine struct IndexView: View { @AppStorage("token") private var token: String = "" @AppStorage("hostname") private var hostname: String = "" @AppStorage("network_code") private var networkCode: String = "" @State private var showToken: Bool = false @ObservedObject private var vpnManager = VPNManager.shared @State private var showAlert = false @State private var showStunAlert = false @State private var message: NoticeMessage.InboundMessage = .none @State private var cancel: AnyCancellable? @State private var showMenu: Bool = false @State private var networkProfile: SDLAPI.NetworkProfile = .init(network: []) @State private var selectedIdx: Int = 0 // 显示ip信息 @State private var showIpAdress: Bool = false @State private var ipAddress: String = "" public var noticeServer: UDPNoticeCenterServer var body: some View { VStack(alignment: .center, spacing: 10) { VStack(alignment: .center, spacing: 10) { Spacer() .frame(height: 100) Image("logo") .resizable() .frame(width: 150, height: 150) Text("Connecting the Infinite") .font(.system(size: 24, weight: .bold)) .foregroundColor(.white) .cornerRadius(5.0) Text("Welcome to PunchNet") .font(.system(size: 14, weight: .regular)) .foregroundColor(.white) .cornerRadius(5.0) } .contentShape(Rectangle()) .onTapGesture { self.showMenu = false } TextField("主机名", text: $hostname) .multilineTextAlignment(.leading) .textFieldStyle(PlainTextFieldStyle()) .frame(width: 200, height: 25) .background(Color.white) .foregroundColor(Color.black) .cornerRadius(5.0) if showIpAdress { HStack { Spacer() Text("ip: ") .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) .cornerRadius(5.0) Text(ipAddress) .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) .cornerRadius(5.0) Spacer() } } Spacer() .frame(width: 1, height: 10) VStack(spacing: 0) { ForEach(Array(networkProfile.network.enumerated()), id: \.offset) { idx, network in NetworkItemView(idx: idx, item: network) .padding(.horizontal, 8) .padding(.vertical, 6) .background( RoundedRectangle(cornerRadius: 8) .fill(selectedIdx == idx ? Color.blue.opacity(0.3) : Color.clear) ) .contentShape(Rectangle()) .onTapGesture { withAnimation(.easeInOut(duration: 0.2)) { selectedIdx = idx self.networkCode = network.code } } } } TextField("邀请码", text: $token) .multilineTextAlignment(.leading) .textFieldStyle(PlainTextFieldStyle()) .frame(width: 200, height: 25) .background(Color.white) .foregroundColor(Color.black) .cornerRadius(5.0) .opacity(showToken ? 1 : 0) Spacer() .frame(width: 1, height: 10) Rectangle() .overlay { Text(vpnManager.title) .font(.system(size: 14, weight: .regular)) .foregroundColor(vpnManager.color) } .frame(width: 120, height: 35) .foregroundColor(Color(red: 74 / 255, green: 207 / 255, blue: 154 / 255)) .cornerRadius(5.0) .onTapGesture { Task { do { try await self.clickSwitchButton() } catch let err { NSLog("start vpn get error: \(err)") } } } } .overlay(alignment: .top) { HStack(spacing: 200) { HStack { Button(action: { NSApplication.shared.terminate(nil) }) { Image("close") .resizable() .frame(width: 15, height: 15) } .buttonStyle(PlainButtonStyle()) Button(action: { NSApplication.shared.keyWindow?.miniaturize(nil) }) { Image("line") .resizable() .frame(width: 15, height: 15) } .buttonStyle(PlainButtonStyle()) } Button(action: { showMenu.toggle() }) { Image("IosSettings") .resizable() .frame(width: 20, height: 20) } .buttonStyle(PlainButtonStyle()) .overlay(alignment: .leading) { showMenu ? GeometryReader { geometry in VStack(alignment: .leading, spacing: 8) { Button(action: { self.showMenu = false }) { Text("主页") .font(.system(size: 14)) .foregroundColor(.white) } .buttonStyle(PlainButtonStyle()) Button(action: { self.showToken.toggle() }) { Text("邀请码") .font(.system(size: 14)) .foregroundColor(.white) } .buttonStyle(PlainButtonStyle()) Button(action: { NSApplication.shared.terminate(nil) }) { Text("退出") .font(.system(size: 14)) .foregroundColor(.white) } .buttonStyle(PlainButtonStyle()) } .frame(width: 90, height: 80) .background(Color(red: 50 / 255, green: 55 / 255, blue: 52 / 255)) .offset(x: -55, y: 20) } : nil } } .offset(x: 0, y: 10) } .padding([.leading, .trailing, .top], 10) .padding([.bottom], 20) .background(Color(red: 36 / 255, green: 38 / 255, blue: 51 / 255)) .frame(width: 320) .alert(isPresented: $showAlert) { Alert(title: Text("请输入正确的邀请码")) } .alert(isPresented: $showStunAlert) { switch self.message { case .upgradeMessage(let prompt, _): Alert(title: Text(prompt)) case .alertMessage(let alert): Alert(title: Text(alert)) default: Alert(title: Text("")) } } .task { do { let response = try await SDLAPI.getUserNetworks(clientId: SystemConfig.getClientId()) print("get user networks: \(response)") if let result = response.result { self.networkProfile = result if self.networkProfile.network.count > 0 { self.networkCode = self.networkProfile.network[0].code } } } catch let err { NSLog("get user networks get error: \(err)") } } .onAppear { self.cancel = self.noticeServer.messageFlow.sink{ message in DispatchQueue.main.async { switch message { case .none: () case .ip(let ip): self.showIpAdress = true self.ipAddress = ip default: self.message = message self.showStunAlert = true } } } } } private func clickSwitchButton() async throws { switch self.vpnManager.vpnStatus { case .connected: self.showIpAdress = false self.ipAddress = "" try await vpnManager.disableVpn() case .disconnected: // let clientId = SystemConfig.getClientId() // NSLog("[IndexView] use token: \(self.token), network_code: \(networkCode)") // // token存在则优先使用token // try await vpnManager.enableVpn(options: SystemConfig.getOptions(networkCode: self.networkCode, token: self.token, clientId: clientId, hostname: self.hostname, noticePort: self.noticeServer.port)!) () } } } extension IndexView { struct NetworkItemView: View { let idx: Int let item: SDLAPI.NetworkProfile.NetworkItem var body: some View { HStack { Text(item.name) .font(.system(size: 14)) .foregroundColor(.white) .frame(width: 80, alignment: .leading) Text(item.code) .font(.system(size: 14)) .foregroundColor(.white) Spacer() } } } } #Preview { let server = UDPNoticeCenterServer() IndexView(noticeServer: server) //.modelContainer(for: Item.self, inMemory: true) }