From 90368feb81c399cf7432e94c691521d4836fa11c Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Tue, 16 May 2023 14:20:23 +0800 Subject: [PATCH] feat: 3-column proxies --- ClashX Dashboard.xcodeproj/project.pbxproj | 20 +- ClashX Dashboard/Models/DBProxyStorage.swift | 121 ++++++ .../ContentTabs/Proxies/ProxiesView.swift | 70 +--- .../Proxies/ProxyGroupRowView.swift | 45 ++ .../ContentTabs/Proxies/ProxyGroupView.swift | 141 ++++--- .../ContentTabs/Proxies/ProxyItemData.swift | 75 ---- .../ContentTabs/Proxies/ProxyItemView.swift | 4 +- .../Proxies/ProxyProviderGroupView.swift | 384 +++++++++--------- 8 files changed, 471 insertions(+), 389 deletions(-) create mode 100644 ClashX Dashboard/Models/DBProxyStorage.swift create mode 100644 ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift delete mode 100644 ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemData.swift diff --git a/ClashX Dashboard.xcodeproj/project.pbxproj b/ClashX Dashboard.xcodeproj/project.pbxproj index ff8c0ca..c1fa00a 100644 --- a/ClashX Dashboard.xcodeproj/project.pbxproj +++ b/ClashX Dashboard.xcodeproj/project.pbxproj @@ -11,12 +11,12 @@ 0140D8F029E6D3C800A515E8 /* ProxyProviderGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0140D8EF29E6D3C800A515E8 /* ProxyProviderGroupView.swift */; }; 0155D39629F2342F00869830 /* TrafficGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155D39529F2342F00869830 /* TrafficGraphView.swift */; }; 0155D39829F23BDE00869830 /* OverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155D39729F23BDE00869830 /* OverviewView.swift */; }; - 0172CB4D29E542410072DDEF /* ProxyItemData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172CB4C29E542410072DDEF /* ProxyItemData.swift */; }; 0172CB4F29E562960072DDEF /* ClearBackgroundList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172CB4E29E562960072DDEF /* ClearBackgroundList.swift */; }; 0172CB5129E5AE670072DDEF /* SwiftUIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */; }; 017753C029EF7FB2006999DB /* APIServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017753BF29EF7FB1006999DB /* APIServerItem.swift */; }; 017DCADD29E83BFD00B9622A /* RuleProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DCADC29E83BFD00B9622A /* RuleProviderView.swift */; }; 017F9AAA2A0DFEBD00B81497 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 017F9AA92A0DFEBD00B81497 /* Introspect */; }; + 017F9AAC2A0E0B2300B81497 /* ProxyGroupRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */; }; 018A61BD29E9A2ED008608C0 /* APISettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018A61BC29E9A2ED008608C0 /* APISettingView.swift */; }; 018C836C29E17505006366D3 /* ClashApiDatasStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018C836B29E17505006366D3 /* ClashApiDatasStorage.swift */; }; 0192315F29DD4DCF00539EDD /* ClashX_DashboardApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192315E29DD4DCF00539EDD /* ClashX_DashboardApp.swift */; }; @@ -52,6 +52,7 @@ 019D6A9629F194C600A6AC02 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 019D6A9529F194C600A6AC02 /* DSFSparkline */; }; 01A351A229DD8F440054894E /* RuleItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A351A129DD8F440054894E /* RuleItemView.swift */; }; 01A351A929DD9CB00054894E /* Connections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A351A829DD9CB00054894E /* Connections.swift */; }; + 01A3EF042A120103003038B5 /* DBProxyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A3EF032A120103003038B5 /* DBProxyStorage.swift */; }; 01CD0A9229E93ABB00F4C17E /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 01CD0A9129E93ABB00F4C17E /* DifferenceKit */; }; 01F885CF29DFD8DF008241EB /* CollectionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F885CE29DFD8DF008241EB /* CollectionsTableView.swift */; }; 01F885D129E03F20008241EB /* CollectionTableCellView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 01F885D029E03F20008241EB /* CollectionTableCellView.xib */; }; @@ -64,11 +65,11 @@ 0140D8EF29E6D3C800A515E8 /* ProxyProviderGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyProviderGroupView.swift; sourceTree = ""; }; 0155D39529F2342F00869830 /* TrafficGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficGraphView.swift; sourceTree = ""; }; 0155D39729F23BDE00869830 /* OverviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = ""; }; - 0172CB4C29E542410072DDEF /* ProxyItemData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyItemData.swift; sourceTree = ""; }; 0172CB4E29E562960072DDEF /* ClearBackgroundList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearBackgroundList.swift; sourceTree = ""; }; 0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewExtensions.swift; sourceTree = ""; }; 017753BF29EF7FB1006999DB /* APIServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIServerItem.swift; sourceTree = ""; }; 017DCADC29E83BFD00B9622A /* RuleProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProviderView.swift; sourceTree = ""; }; + 017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupRowView.swift; sourceTree = ""; }; 018A61BC29E9A2ED008608C0 /* APISettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISettingView.swift; sourceTree = ""; }; 018C836B29E17505006366D3 /* ClashApiDatasStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashApiDatasStorage.swift; sourceTree = ""; }; 0192315B29DD4DCF00539EDD /* ClashX Dashboard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ClashX Dashboard.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -101,6 +102,7 @@ 019D6A8629F015DF00A6AC02 /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = ""; }; 01A351A129DD8F440054894E /* RuleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleItemView.swift; sourceTree = ""; }; 01A351A829DD9CB00054894E /* Connections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connections.swift; sourceTree = ""; }; + 01A3EF032A120103003038B5 /* DBProxyStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProxyStorage.swift; sourceTree = ""; }; 01F885CE29DFD8DF008241EB /* CollectionsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionsTableView.swift; sourceTree = ""; }; 01F885D029E03F20008241EB /* CollectionTableCellView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectionTableCellView.xib; sourceTree = ""; }; 01F885D229E04E21008241EB /* ProxyGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupView.swift; sourceTree = ""; }; @@ -178,6 +180,7 @@ isa = PBXGroup; children = ( 0192315E29DD4DCF00539EDD /* ClashX_DashboardApp.swift */, + 01A3EF022A120032003038B5 /* Models */, 018C836A29E174CB006366D3 /* Views */, 0192B5B529DE506D002CDBF3 /* ClashX Links */, 0192316229DD4DD100539EDD /* Assets.xcassets */, @@ -296,10 +299,10 @@ isa = PBXGroup; children = ( 0192317729DD5DA500539EDD /* ProxiesView.swift */, + 017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */, 01F885D229E04E21008241EB /* ProxyGroupView.swift */, 0140D8EF29E6D3C800A515E8 /* ProxyProviderGroupView.swift */, 01F885D429E053DE008241EB /* ProxyItemView.swift */, - 0172CB4C29E542410072DDEF /* ProxyItemData.swift */, ); path = Proxies; sourceTree = ""; @@ -315,6 +318,14 @@ path = Connections; sourceTree = ""; }; + 01A3EF022A120032003038B5 /* Models */ = { + isa = PBXGroup; + children = ( + 01A3EF032A120103003038B5 /* DBProxyStorage.swift */, + ); + path = Models; + sourceTree = ""; + }; 01A7335F29E2CBD600205699 /* Config */ = { isa = PBXGroup; children = ( @@ -417,12 +428,13 @@ 0192316129DD4DCF00539EDD /* ContentView.swift in Sources */, 0192318729DD83FF00539EDD /* OverviewTopItemView.swift in Sources */, 0172CB4F29E562960072DDEF /* ClearBackgroundList.swift in Sources */, - 0172CB4D29E542410072DDEF /* ProxyItemData.swift in Sources */, 0192318029DD5E0B00539EDD /* LogsView.swift in Sources */, 0192318529DD7DCD00539EDD /* SidebarItemView.swift in Sources */, 0192317E29DD5E0100539EDD /* ConfigView.swift in Sources */, 0155D39629F2342F00869830 /* TrafficGraphView.swift in Sources */, + 01A3EF042A120103003038B5 /* DBProxyStorage.swift in Sources */, 0140D8F029E6D3C800A515E8 /* ProxyProviderGroupView.swift in Sources */, + 017F9AAC2A0E0B2300B81497 /* ProxyGroupRowView.swift in Sources */, 017DCADD29E83BFD00B9622A /* RuleProviderView.swift in Sources */, 0192318329DD70B400539EDD /* SidebarItem.swift in Sources */, 0155D39829F23BDE00869830 /* OverviewView.swift in Sources */, diff --git a/ClashX Dashboard/Models/DBProxyStorage.swift b/ClashX Dashboard/Models/DBProxyStorage.swift new file mode 100644 index 0000000..48578e4 --- /dev/null +++ b/ClashX Dashboard/Models/DBProxyStorage.swift @@ -0,0 +1,121 @@ +// +// DBProxyStorage.swift +// ClashX Dashboard +// +// + +import Cocoa +import SwiftUI + +class DBProxyStorage: ObservableObject { + @Published var groups = [DBProxyGroup]() + + init() { + + } + + init(_ resp: ClashProxyResp) { + groups = resp.proxyGroups.map { + DBProxyGroup($0, resp: resp) + } + } +} + +class DBProxyGroup: ObservableObject, Identifiable { + let id: String + @Published var name: ClashProxyName + @Published var type: ClashProxyType + @Published var now: ClashProxyName? { + didSet { + currentProxy = proxies.first { + $0.name == now + } + } + } + + @Published var proxies: [DBProxy] + + @Published var currentProxy: DBProxy? + + init(_ group: ClashProxy, resp: ClashProxyResp) { + id = group.id + name = group.name + type = group.type + now = group.now + + proxies = group.all?.compactMap { name in + resp.proxiesMap[name] + }.map(DBProxy.init) ?? [] + + currentProxy = proxies.first { + $0.name == now + } + } +} + +class DBProxy: ObservableObject { + let id: String + @Published var name: ClashProxyName + @Published var type: ClashProxyType + @Published var udpString: String + @Published var tfo: Bool + + var delay: Int { + didSet { + delayString = DBProxy.delayString(delay) + delayColor = DBProxy.delayColor(delay) + } + } + + @Published var delayString: String + @Published var delayColor: Color + + init(_ proxy: ClashProxy) { + id = proxy.id + name = proxy.name + type = proxy.type + tfo = proxy.tfo + delay = proxy.history.last?.delayInt ?? 0 + + udpString = { + if proxy.udp { + return "UDP" + } else if proxy.xudp { + return "XUDP" + } else { + return "" + } + }() + delayString = DBProxy.delayString(delay) + delayColor = DBProxy.delayColor(delay) + } + + static func delayString(_ delay: Int) -> String { + switch delay { + case 0: + return NSLocalizedString("fail", comment: "") + default: + return "\(delay) ms" + } + } + + static func delayColor(_ delay: Int) -> Color { + let httpsTest = true + + switch delay { + case 0: + return .gray + case ..<200 where !httpsTest: + return .green + case ..<800 where httpsTest: + return .green + case 200..<500 where !httpsTest: + return .yellow + case 800..<1500 where httpsTest: + return .yellow + default: + return .orange + } + } +} + diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift index 40c8125..2b3e1fe 100644 --- a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift +++ b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift @@ -5,6 +5,7 @@ // import SwiftUI +import Introspect class ProxiesSearchString: ObservableObject, Identifiable { let id = UUID().uuidString @@ -13,72 +14,37 @@ class ProxiesSearchString: ObservableObject, Identifiable { struct ProxiesView: View { - @State var proxyInfo: ClashProxyResp? - @State var proxyGroups = [ClashProxy]() - - @State var providerInfo: ClashProviderResp? - @State var providers = [ClashProvider]() - -// @State var proxyProviderList + @ObservedObject var proxyStorage = DBProxyStorage() @State private var searchString = ProxiesSearchString() @State private var isGlobalMode = false - @State private var proxyListColumnCount = 3 var body: some View { - List() { - Text("Proxies") - .font(.title) - ForEach(proxyGroups, id: \.id) { group in - ProxyGroupView(columnCount: $proxyListColumnCount, proxyGroup: group, proxyInfo: proxyInfo!) + NavigationView { + List(proxyStorage.groups, id: \.id) { group in + ProxyGroupRowView(proxyGroup: group) } - - Text("Proxy Provider") - .font(.title) - .padding(.top) - - ForEach($providers, id: \.id) { provider in - ProxyProviderGroupView(columnCount: $proxyListColumnCount, providerInfo: provider) + .introspectTableView { + $0.refusesFirstResponder = true + $0.doubleAction = nil } - } - .background { - GeometryReader { geometry in - Rectangle() - .fill(.clear) - .frame(height: 1) - .onChange(of: geometry.size.width) { newValue in - updateColumnCount(newValue) - } - .onAppear { - updateColumnCount(geometry.size.width) - } - }.padding() + .listStyle(.plain) + EmptyView() } .searchable(text: $searchString.string) .environmentObject(searchString) .onAppear { - -// self.isGlobalMode = ConfigManager.shared.currentConfig?.mode == .global - ApiRequest.getMergedProxyData { - proxyInfo = $0 - proxyGroups = ($0?.proxyGroups ?? []).filter { - isGlobalMode ? true : $0.name != "GLOBAL" - } - - providerInfo = proxyInfo?.enclosingProviderResp - providers = providerInfo?.providers.map { - $0.value - } ?? [] - } + loadProxies() } } - func updateColumnCount(_ width: Double) { - let v = Int(Int(width) / 200) - let new = v == 0 ? 1 : v - - if new != proxyListColumnCount { - proxyListColumnCount = new + + func loadProxies() { +// self.isGlobalMode = ConfigManager.shared.currentConfig?.mode == .global + ApiRequest.requestProxyGroupList { + proxyStorage.groups = DBProxyStorage($0).groups.filter { + isGlobalMode ? true : $0.name != "GLOBAL" + } } } } diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift new file mode 100644 index 0000000..816b4c5 --- /dev/null +++ b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift @@ -0,0 +1,45 @@ +// +// ProxyGroupInfoView.swift +// ClashX Dashboard +// +// + +import SwiftUI + +struct ProxyGroupRowView: View { + + @ObservedObject var proxyGroup: DBProxyGroup + + var body: some View { + NavigationLink { + ProxyGroupView(proxyGroup: proxyGroup) + } label: { + labelView + } + } + + var labelView: some View { + VStack(spacing: 2) { + HStack(alignment: .center) { + Text(proxyGroup.name) + .font(.system(size: 15)) + Spacer() + if let proxy = proxyGroup.currentProxy { + Text(proxy.delayString) + .foregroundColor(proxy.delayColor) + .font(.system(size: 12)) + } + } + + HStack { + Text(proxyGroup.type.rawValue) + Spacer() + Text(proxyGroup.now ?? "") + } + .font(.system(size: 11)) + .foregroundColor(.secondary) + } + .padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4)) + } +} + diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift index 6bec14c..1e1d7e9 100644 --- a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift +++ b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift @@ -8,68 +8,83 @@ import SwiftUI struct ProxyGroupView: View { - @Binding var columnCount: Int - - @State var proxyGroup: ClashProxy - @State var proxyInfo: ClashProxyResp - @State private var proxyItems: [ProxyItemData] - @State private var currentProxy: ClashProxyName - @State private var isUpdatingSelect = false - @State private var selectable = false - - @State private var isListExpanded = false - @State private var isTesting = false - + @ObservedObject var proxyGroup: DBProxyGroup @EnvironmentObject var searchString: ProxiesSearchString - init(columnCount: Binding, - proxyGroup: ClashProxy, - proxyInfo: ClashProxyResp) { + @State private var columnCount: Int = 3 + @State private var isUpdatingSelect = false + @State private var selectable = false + @State private var isTesting = false + + var body: some View { + ScrollView { + Section { + proxyListView + } header: { + proxyInfoView + } + .padding() + } - self._columnCount = columnCount - self.proxyGroup = proxyGroup - self.proxyInfo = proxyInfo - self.currentProxy = proxyGroup.now ?? "" - self.selectable = [.select, .fallback].contains(proxyGroup.type) - - self.proxyItems = proxyGroup.all?.compactMap { name in - proxyInfo.proxiesMap[name] - }.map(ProxyItemData.init) ?? [] + .background { + GeometryReader { geometry in + Rectangle() + .fill(.clear) + .frame(height: 1) + .onChange(of: geometry.size.width) { newValue in + updateColumnCount(newValue) + } + .onAppear { + updateColumnCount(geometry.size.width) + } + }.padding() + } + .onAppear { + self.selectable = [.select, .fallback].contains(proxyGroup.type) + } } - - var body: some View { - Section { - proxyListView - .background { - Rectangle() - .frame(width: 2, height: listHeight(columnCount)) - .foregroundColor(.clear) - } - .show(isVisible: !isListExpanded) - - } header: { - proxyInfoView + func updateColumnCount(_ width: Double) { + let v = Int(Int(width) / 180) + let new = v == 0 ? 1 : v + + if new != columnCount { + columnCount = new } - } + } + var proxyInfoView: some View { HStack() { Text(proxyGroup.name) - .font(.title) - .fontWeight(.medium) + .font(.system(size: 17)) Text(proxyGroup.type.rawValue) - Text("\(proxyGroup.all?.count ?? 0)") - Button() { - isListExpanded = !isListExpanded - } label: { - Image(systemName: isListExpanded ? "chevron.up" : "chevron.down") - } + .font(.system(size: 13)) + .foregroundColor(.secondary) + Text("\(proxyGroup.proxies.count)") + .font(.system(size: 11)) + .padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)) + .background(Color.gray.opacity(0.5)) + .cornerRadius(4) + + Spacer() Button() { startBenchmark() } label: { - Image(systemName: "bolt.fill") + HStack { + if isTesting { + ProgressView() + .controlSize(.small) + .frame(width: 12) + } else { + Image(systemName: "bolt.fill") + .frame(width: 12) + } + Text(isTesting ? "Testing" : (proxyGroup.type == .urltest ? "Retest" : "Benchmark")) + .frame(width: 70) + } + .foregroundColor(isTesting ? .gray : .blue) } .disabled(isTesting) } @@ -78,46 +93,44 @@ struct ProxyGroupView: View { var proxyListView: some View { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount)) { - ForEach($proxyItems, id: \.id) { item in + ForEach($proxyGroup.proxies, id: \.id) { proxy in ProxyItemView( - proxy: item, + proxy: proxy, selectable: selectable ) - .background(currentProxy == item.wrappedValue.name ? Color.teal : Color.white) + .background(proxyGroup.now == proxy.wrappedValue.name ? Color.teal : Color.white) .cornerRadius(8) .onTapGesture { - let item = item.wrappedValue + let item = proxy.wrappedValue updateSelect(item.name) } .show(isVisible: { if searchString.string.isEmpty { return true } else { - return item.wrappedValue.name.lowercased().contains(searchString.string.lowercased()) + return proxy.wrappedValue.name.lowercased().contains(searchString.string.lowercased()) } }()) } } } - - func listHeight(_ columnCount: Int) -> Double { - let lineCount = ceil(Double(proxyItems.count) / Double(columnCount)) - return lineCount * 60 + (lineCount - 1) * 8 - } - func startBenchmark() { isTesting = true ApiRequest.getGroupDelay(groupName: proxyGroup.name) { delays in - proxyGroup.all?.forEach { proxyName in + proxyGroup.proxies.enumerated().forEach { var delay = 0 - if let d = delays[proxyName], d != 0 { + if let d = delays[$0.element.name], d != 0 { delay = d } + guard $0.offset < proxyGroup.proxies.count, + proxyGroup.proxies[$0.offset].name == $0.element.name + else { return } + proxyGroup.proxies[$0.offset].delay = delay - proxyItems.first { - $0.name == proxyName - }?.delay = delay + if proxyGroup.currentProxy?.name == $0.element.name { + proxyGroup.currentProxy = proxyGroup.proxies[$0.offset] + } } isTesting = false } @@ -129,7 +142,7 @@ struct ProxyGroupView: View { ApiRequest.updateProxyGroup(group: proxyGroup.name, selectProxy: name) { success in isUpdatingSelect = false guard success else { return } - currentProxy = name + proxyGroup.now = name } } diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemData.swift b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemData.swift deleted file mode 100644 index 8eb1013..0000000 --- a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemData.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// ProxyItemData.swift -// ClashX Dashboard -// -// - -import Cocoa -import SwiftUI - -class ProxyItemData: NSObject, ObservableObject { - let id: String - @objc let name: ClashProxyName - let type: ClashProxyType - let udpString: String - let tfo: Bool - let all: [ClashProxyName] - - var delay: Int { - didSet { - switch delay { - case 0: - delayString = NSLocalizedString("fail", comment: "") - default: - delayString = "\(delay) ms" - } - - let httpsTest = true - - switch delay { - case 0: - delayColor = .gray - case ..<200 where !httpsTest: - delayColor = .green - case ..<800 where httpsTest: - delayColor = .green - case 200..<500 where !httpsTest: - delayColor = .yellow - case 800..<1500 where httpsTest: - delayColor = .yellow - default: - delayColor = .orange - } - } - } - - @Published var delayString = "" - @Published var delayColor = Color.clear - - init(clashProxy: ClashProxy) { - id = clashProxy.id - name = clashProxy.name - type = clashProxy.type - tfo = clashProxy.tfo - all = clashProxy.all ?? [] - - - udpString = { - if clashProxy.udp { - return "UDP" - } else if clashProxy.xudp { - return "XUDP" - } else { - return "" - } - }() - - delay = 0 - super.init() - defer { - delay = clashProxy.history.last?.meanDelay ?? clashProxy.history.last?.delay ?? 0 - } - - - } -} diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemView.swift b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemView.swift index f9c780a..d2e28f2 100644 --- a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemView.swift +++ b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemView.swift @@ -8,10 +8,10 @@ import SwiftUI struct ProxyItemView: View { - @Binding var proxy: ProxyItemData + @Binding var proxy: DBProxy @State var selectable: Bool - init(proxy: Binding, selectable: Bool) { + init(proxy: Binding, selectable: Bool) { self._proxy = proxy self.selectable = selectable diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyProviderGroupView.swift b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyProviderGroupView.swift index 8f05b1a..9f32a6d 100644 --- a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyProviderGroupView.swift +++ b/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyProviderGroupView.swift @@ -6,199 +6,199 @@ import SwiftUI -struct ProxyProviderGroupView: View { - @Binding var columnCount: Int - - @Binding var providerInfo: ClashProvider - - @State private var proxyItems: [ProxyItemData] - - @State private var trafficInfo: String - @State private var expireDate: String - @State private var updateAt: String - - - @State private var isListExpanded = false - @State private var isTesting = false - @State private var isUpdating = false - - @EnvironmentObject var searchString: ProxiesSearchString - - init(columnCount: Binding, - providerInfo: Binding) { - self._columnCount = columnCount - self._providerInfo = providerInfo - - let info = providerInfo.wrappedValue - - self.proxyItems = info.proxies.map(ProxyItemData.init) - - if let info = info.subscriptionInfo { - let used = info.download + info.upload - let total = info.total - - let formatter = ByteCountFormatter() - - trafficInfo = formatter.string(fromByteCount: used) - + " / " - + formatter.string(fromByteCount: total) - + " ( \(String(format: "%.2f", Double(used)/Double(total/100)))% )" - - - let expire = info.expire - - expireDate = "Expire: " - + Date(timeIntervalSince1970: TimeInterval(expire)) - .formatted() - } else { - trafficInfo = "" - expireDate = "" - } - - if let updatedAt = info.updatedAt { - let formatter = RelativeDateTimeFormatter() - self.updateAt = formatter.localizedString(for: updatedAt, relativeTo: .now) - } else { - self.updateAt = "" - } - } - - - var body: some View { - Section { - providerListView - .background { - Rectangle() - .frame(width: 2, height: listHeight(columnCount)) - .foregroundColor(.clear) - } - .show(isVisible: !isListExpanded) - - } header: { - providerInfoView - } footer: { - HStack { - Button { - update() - } label: { - Label("Update", systemImage: "arrow.clockwise") - } - - .disabled(isUpdating) - - Button { - startBenchmark() - } label: { - Label("Benchmark", systemImage: "bolt.fill") - } - .disabled(isTesting) - } - } - } - - var providerInfoView: some View { - VStack(alignment: .leading) { - HStack { - Text(providerInfo.name) - .font(.title) - .fontWeight(.medium) - Text(providerInfo.vehicleType.rawValue) - .fontWeight(.regular) - Text("\(providerInfo.proxies.count)") - Button() { - isListExpanded = !isListExpanded - } label: { - Image(systemName: isListExpanded ? "chevron.up" : "chevron.down") - } - Button() { - update() - } label: { - Image(systemName: "arrow.clockwise") - } - .disabled(isUpdating) - - Button() { - startBenchmark() - } label: { - Image(systemName: "bolt.fill") - } - .disabled(isTesting) - } - - HStack { - if trafficInfo != "" { - Text(trafficInfo) - .fontWeight(.regular) - } - if expireDate != "" { - Text(expireDate) - .fontWeight(.regular) - } - } - if updateAt != "" { - Text("Updated \(updateAt)") - .fontWeight(.regular) - } - } - } - - var providerListView: some View { - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), - count: columnCount)) { - ForEach($proxyItems, id: \.id) { item in - ProxyItemView( - proxy: item, - selectable: false - ) - .background(.white) - .cornerRadius(8) -// .onTapGesture { -// let item = item.wrappedValue -// updateSelect(item.name) +//struct ProxyProviderGroupView: View { +// @Binding var columnCount: Int +// +// @Binding var providerInfo: ClashProvider +// +// @State private var proxyItems: [ProxyItemData] +// +// @State private var trafficInfo: String +// @State private var expireDate: String +// @State private var updateAt: String +// +// +// @State private var isListExpanded = false +// @State private var isTesting = false +// @State private var isUpdating = false +// +// @EnvironmentObject var searchString: ProxiesSearchString +// +// init(columnCount: Binding, +// providerInfo: Binding) { +// self._columnCount = columnCount +// self._providerInfo = providerInfo +// +// let info = providerInfo.wrappedValue +// +// self.proxyItems = info.proxies.map(ProxyItemData.init) +// +// if let info = info.subscriptionInfo { +// let used = info.download + info.upload +// let total = info.total +// +// let formatter = ByteCountFormatter() +// +// trafficInfo = formatter.string(fromByteCount: used) +// + " / " +// + formatter.string(fromByteCount: total) +// + " ( \(String(format: "%.2f", Double(used)/Double(total/100)))% )" +// +// +// let expire = info.expire +// +// expireDate = "Expire: " +// + Date(timeIntervalSince1970: TimeInterval(expire)) +// .formatted() +// } else { +// trafficInfo = "" +// expireDate = "" +// } +// +// if let updatedAt = info.updatedAt { +// let formatter = RelativeDateTimeFormatter() +// self.updateAt = formatter.localizedString(for: updatedAt, relativeTo: .now) +// } else { +// self.updateAt = "" +// } +// } +// +// +// var body: some View { +// Section { +// providerListView +// .background { +// Rectangle() +// .frame(width: 2, height: listHeight(columnCount)) +// .foregroundColor(.clear) // } - .show(isVisible: { - if searchString.string.isEmpty { - return true - } else { - return item.wrappedValue.name.lowercased().contains(searchString.string.lowercased()) - } - }()) - } - } - } - - func listHeight(_ columnCount: Int) -> Double { - let lineCount = ceil(Double(providerInfo.proxies.count) / Double(columnCount)) - return lineCount * 60 + (lineCount - 1) * 8 - } - - func startBenchmark() { - isTesting = true - let name = providerInfo.name - ApiRequest.healthCheck(proxy: name) { - ApiRequest.requestProxyProviderList { - isTesting = false - - guard let provider = $0.allProviders[name] else { return } - self.proxyItems = provider.proxies.map(ProxyItemData.init) - } - } - } - - func update() { - isUpdating = true - let name = providerInfo.name - ApiRequest.updateProvider(for: .proxy, name: name) { _ in - ApiRequest.requestProxyProviderList { - isUpdating = false - guard let provider = $0.allProviders[name] else { return } - self.providerInfo = provider - } - } - } - - -} +// .show(isVisible: !isListExpanded) +// +// } header: { +// providerInfoView +// } footer: { +// HStack { +// Button { +// update() +// } label: { +// Label("Update", systemImage: "arrow.clockwise") +// } +// +// .disabled(isUpdating) +// +// Button { +// startBenchmark() +// } label: { +// Label("Benchmark", systemImage: "bolt.fill") +// } +// .disabled(isTesting) +// } +// } +// } +// +// var providerInfoView: some View { +// VStack(alignment: .leading) { +// HStack { +// Text(providerInfo.name) +// .font(.title) +// .fontWeight(.medium) +// Text(providerInfo.vehicleType.rawValue) +// .fontWeight(.regular) +// Text("\(providerInfo.proxies.count)") +// Button() { +// isListExpanded = !isListExpanded +// } label: { +// Image(systemName: isListExpanded ? "chevron.up" : "chevron.down") +// } +// Button() { +// update() +// } label: { +// Image(systemName: "arrow.clockwise") +// } +// .disabled(isUpdating) +// +// Button() { +// startBenchmark() +// } label: { +// Image(systemName: "bolt.fill") +// } +// .disabled(isTesting) +// } +// +// HStack { +// if trafficInfo != "" { +// Text(trafficInfo) +// .fontWeight(.regular) +// } +// if expireDate != "" { +// Text(expireDate) +// .fontWeight(.regular) +// } +// } +// if updateAt != "" { +// Text("Updated \(updateAt)") +// .fontWeight(.regular) +// } +// } +// } +// +// var providerListView: some View { +// LazyVGrid(columns: Array(repeating: GridItem(.flexible()), +// count: columnCount)) { +// ForEach($proxyItems, id: \.id) { item in +// ProxyItemView( +// proxy: item, +// selectable: false +// ) +// .background(.white) +// .cornerRadius(8) +//// .onTapGesture { +//// let item = item.wrappedValue +//// updateSelect(item.name) +//// } +// .show(isVisible: { +// if searchString.string.isEmpty { +// return true +// } else { +// return item.wrappedValue.name.lowercased().contains(searchString.string.lowercased()) +// } +// }()) +// } +// } +// } +// +// func listHeight(_ columnCount: Int) -> Double { +// let lineCount = ceil(Double(providerInfo.proxies.count) / Double(columnCount)) +// return lineCount * 60 + (lineCount - 1) * 8 +// } +// +// func startBenchmark() { +// isTesting = true +// let name = providerInfo.name +// ApiRequest.healthCheck(proxy: name) { +// ApiRequest.requestProxyProviderList { +// isTesting = false +// +// guard let provider = $0.allProviders[name] else { return } +// self.proxyItems = provider.proxies.map(ProxyItemData.init) +// } +// } +// } +// +// func update() { +// isUpdating = true +// let name = providerInfo.name +// ApiRequest.updateProvider(for: .proxy, name: name) { _ in +// ApiRequest.requestProxyProviderList { +// isUpdating = false +// guard let provider = $0.allProviders[name] else { return } +// self.providerInfo = provider +// } +// } +// } +// +// +//} //struct ProviderGroupView_Previews: PreviewProvider { // static var previews: some View {