tools: Network Quality and STUN
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public struct NetworkQualityView: View {
|
||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||
@StateObject private var viewModel = NetworkQualityViewModel()
|
||||
|
||||
public init() {}
|
||||
|
||||
private var downloadActive: Bool {
|
||||
(viewModel.isRunning && !viewModel.serial && viewModel.phase >= LibboxNetworkQualityPhaseDownload && viewModel.phase < LibboxNetworkQualityPhaseDone)
|
||||
|| viewModel.phase == LibboxNetworkQualityPhaseDownload
|
||||
}
|
||||
|
||||
private func accuracyLabel(_ value: Int32) -> (label: String, color: Color) {
|
||||
switch value {
|
||||
case LibboxNetworkQualityAccuracyHigh:
|
||||
return (String(localized: "Confidence High"), .green)
|
||||
case LibboxNetworkQualityAccuracyMedium:
|
||||
return (String(localized: "Confidence Medium"), .yellow)
|
||||
default:
|
||||
return (String(localized: "Confidence Low"), .red)
|
||||
}
|
||||
}
|
||||
|
||||
private var uploadActive: Bool {
|
||||
(viewModel.isRunning && !viewModel.serial && viewModel.phase >= LibboxNetworkQualityPhaseDownload && viewModel.phase < LibboxNetworkQualityPhaseDone)
|
||||
|| viewModel.phase == LibboxNetworkQualityPhaseUpload
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func resultValue(_ value: String?, active: Bool, accuracy: (label: String, color: Color)? = nil) -> some View {
|
||||
if let value {
|
||||
HStack(spacing: 6) {
|
||||
if viewModel.isRunning, active {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
}
|
||||
Text(value)
|
||||
if let accuracy {
|
||||
Text(accuracy.label)
|
||||
.font(.caption)
|
||||
.foregroundColor(accuracy.color)
|
||||
}
|
||||
}
|
||||
} else if viewModel.isRunning, active {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
} else {
|
||||
Text(verbatim: "-")
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
FormView {
|
||||
Section("Configuration") {
|
||||
#if os(tvOS)
|
||||
FormTextItem("URL", "link") {
|
||||
Text(viewModel.configURL)
|
||||
}
|
||||
#else
|
||||
FormItem("URL") {
|
||||
TextField(text: $viewModel.configURL) {}
|
||||
.multilineTextAlignment(.trailing)
|
||||
.autocorrectionDisabled()
|
||||
#if os(iOS)
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.URL)
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
Toggle("Serial", isOn: $viewModel.serial)
|
||||
.disabled(viewModel.isRunning)
|
||||
Toggle("HTTP/3", isOn: $viewModel.http3)
|
||||
.disabled(viewModel.isRunning)
|
||||
Picker("Max Runtime", selection: $viewModel.maxRuntime) {
|
||||
ForEach(MaxRuntimeOption.allCases) { option in
|
||||
Text(option.label).tag(option)
|
||||
}
|
||||
}
|
||||
.disabled(viewModel.isRunning)
|
||||
if let profile = environments.extensionProfile {
|
||||
ToolOutboundSection(profile: profile, viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
Section("Action") {
|
||||
if viewModel.isRunning {
|
||||
FormButton {
|
||||
viewModel.cancel()
|
||||
} label: {
|
||||
Label("Cancel Test", systemImage: "stop.fill")
|
||||
}
|
||||
} else {
|
||||
FormButton {
|
||||
viewModel.requestStartTest(vpnConnected: environments.extensionProfile?.status.isConnectedStrict == true)
|
||||
} label: {
|
||||
Label("Start Test", systemImage: "play.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.phase >= 0 {
|
||||
Section("Results") {
|
||||
FormTextItem("Idle Latency", "timer") {
|
||||
resultValue(viewModel.idleLatencyMs > 0 ? "\(viewModel.idleLatencyMs) ms" : nil, active: viewModel.phase == LibboxNetworkQualityPhaseIdle)
|
||||
}
|
||||
FormTextItem("Download", "arrow.down.circle") {
|
||||
resultValue(viewModel.downloadCapacity > 0 ? LibboxFormatBitrate(viewModel.downloadCapacity) : nil, active: downloadActive, accuracy: viewModel.phase == LibboxNetworkQualityPhaseDone ? accuracyLabel(viewModel.downloadCapacityAccuracy) : nil)
|
||||
}
|
||||
FormTextItem("Download RPM", "arrow.down.to.line") {
|
||||
resultValue(viewModel.downloadRPM > 0 ? "\(viewModel.downloadRPM)" : nil, active: downloadActive, accuracy: viewModel.phase == LibboxNetworkQualityPhaseDone ? accuracyLabel(viewModel.downloadRPMAccuracy) : nil)
|
||||
}
|
||||
FormTextItem("Upload", "arrow.up.circle") {
|
||||
resultValue(viewModel.uploadCapacity > 0 ? LibboxFormatBitrate(viewModel.uploadCapacity) : nil, active: uploadActive, accuracy: viewModel.phase == LibboxNetworkQualityPhaseDone ? accuracyLabel(viewModel.uploadCapacityAccuracy) : nil)
|
||||
}
|
||||
FormTextItem("Upload RPM", "arrow.up.to.line") {
|
||||
resultValue(viewModel.uploadRPM > 0 ? "\(viewModel.uploadRPM)" : nil, active: uploadActive, accuracy: viewModel.phase == LibboxNetworkQualityPhaseDone ? accuracyLabel(viewModel.uploadRPMAccuracy) : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Network Quality")
|
||||
.task {
|
||||
await viewModel.loadPreferences()
|
||||
}
|
||||
.alert($viewModel.alert)
|
||||
.onDisappear {
|
||||
if viewModel.isRunning {
|
||||
viewModel.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user