mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 10:54:50 +00:00
100 lines
3.5 KiB
Swift
100 lines
3.5 KiB
Swift
import SwiftUI
|
|
|
|
/// Observes only the network observations, with a periodic expiry projection.
|
|
struct ConnectivityCell: View {
|
|
@ObservedObject var module: ServerConnectivity
|
|
let node: CatalogTreeNode
|
|
let workflow: CatalogWorkflow
|
|
var selected = false
|
|
|
|
var body: some View {
|
|
TimelineView(.periodic(from: .now, by: 1)) { context in
|
|
let value = presentation(at: context.date)
|
|
HStack(spacing: 5) {
|
|
Text(value.text)
|
|
.foregroundStyle(
|
|
selected
|
|
? AnyShapeStyle(
|
|
Color(nsColor: .alternateSelectedControlTextColor)
|
|
.opacity(value.expired ? 0.85 : 1))
|
|
: AnyShapeStyle(value.expired ? .secondary : .primary))
|
|
if value.testing {
|
|
ProgressView().controlSize(.mini)
|
|
.tint(selected ? Color(nsColor: .alternateSelectedControlTextColor) : nil)
|
|
}
|
|
}
|
|
.help(value.help)
|
|
.accessibilityElement(children: .ignore)
|
|
.accessibilityLabel(Text(ConnectivityCopy.text("响应延迟")))
|
|
.accessibilityValue(
|
|
Text(
|
|
value.text + " " + value.help + (value.testing ? " " + ConnectivityCopy.text("测试中") : ""))
|
|
)
|
|
}
|
|
}
|
|
|
|
private struct Presentation {
|
|
let text: String
|
|
let help: String
|
|
let expired: Bool
|
|
let testing: Bool
|
|
}
|
|
|
|
private func presentation(at date: Date) -> Presentation {
|
|
let record: ServerConnectivity.Record?
|
|
let text: String
|
|
var prefix = ""
|
|
let summary = module.summary(for: node.connectivityServerIDs, at: date)
|
|
if node.isGroup {
|
|
record = summary.record
|
|
text = record.map { ConnectivityCopy.outcome($0.outcome) } ?? (summary.hasResults ? "—" : "")
|
|
if let id = summary.serverID { prefix = workflow.displayName(for: id) + " · " }
|
|
} else {
|
|
record = module.history(for: node.id).first
|
|
text = record.map { ConnectivityCopy.outcome($0.outcome) } ?? ""
|
|
}
|
|
let expired = record?.isExpired(at: date) ?? false
|
|
let help =
|
|
record.map {
|
|
prefix + $0.finishedAt.formatted(date: .numeric, time: .standard)
|
|
+ (expired ? " · " + ConnectivityCopy.text("已过期") : "")
|
|
}
|
|
?? (summary.hasResults && node.isGroup
|
|
? ConnectivityCopy.text("无成功结果") : ConnectivityCopy.text("尚未测试"))
|
|
return Presentation(text: text, help: help, expired: expired, testing: summary.isTesting)
|
|
}
|
|
}
|
|
|
|
struct ConnectivityTestButton: View {
|
|
@ObservedObject var module: ServerConnectivity
|
|
let node: CatalogTreeNode
|
|
var body: some View {
|
|
let isTesting = node.connectivityServerIDs.contains { module.testing.contains($0) }
|
|
Button(ConnectivityCopy.text(isTesting ? "测试中..." : "延迟测试")) {
|
|
module.test(node.connectivityServerIDs)
|
|
}
|
|
.disabled(
|
|
isTesting || !node.containsServerConfiguration || (!node.isGroup && module.isCooling(node.id))
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Copy is kept at the presentation edge; failures never render raw errors or URLs.
|
|
enum ConnectivityCopy {
|
|
static func text(_ key: String) -> String {
|
|
NSLocalizedString(key, tableName: "Connectivity", bundle: .main, comment: "")
|
|
}
|
|
static func outcome(_ value: ServerConnectivity.Outcome) -> String {
|
|
switch value {
|
|
case .success(let milliseconds): "\(milliseconds) ms"
|
|
case .unsupported: text("配置不支持")
|
|
case .preparationTimeout: text("准备超时")
|
|
case .requestTimeout: text("请求超时")
|
|
case .configuration: text("配置错误")
|
|
case .startup: text("启动错误")
|
|
case .network: text("网络错误")
|
|
case .http(let status): "HTTP \(status)"
|
|
}
|
|
}
|
|
}
|