130 lines
4.8 KiB
Swift
130 lines
4.8 KiB
Swift
import Libbox
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
public struct ConnectionView: View {
|
|
private let connection: Connection
|
|
public init(_ connection: Connection) {
|
|
self.connection = connection
|
|
}
|
|
|
|
private static let timeFormatter: DateFormatter = {
|
|
let formatter = DateFormatter()
|
|
formatter.dateFormat = "HH:mm:ss"
|
|
return formatter
|
|
}()
|
|
|
|
private func format(_ date: Date) -> String {
|
|
Self.timeFormatter.string(from: date)
|
|
}
|
|
|
|
public func formatInterval(_ createdAt: Date, _ closedAt: Date) -> String {
|
|
LibboxFormatDuration(Int64((closedAt.timeIntervalSince1970 - createdAt.timeIntervalSince1970) * 1000))
|
|
}
|
|
|
|
@State private var alert: AlertState?
|
|
@State private var showDetails = false
|
|
|
|
public var body: some View {
|
|
Button {
|
|
showDetails = true
|
|
} label: {
|
|
HStack {
|
|
VStack(alignment: .leading) {
|
|
HStack(alignment: .center) {
|
|
Text("\(connection.network.uppercased()) \(connection.displayDestination)")
|
|
Spacer()
|
|
if connection.closedAt == nil {
|
|
Text("Active").foregroundStyle(.green)
|
|
} else {
|
|
Text("Closed").foregroundStyle(.red)
|
|
}
|
|
}
|
|
.font(.caption2.monospaced().bold())
|
|
.padding([.bottom], 4)
|
|
HStack {
|
|
if let closedAt = connection.closedAt {
|
|
VStack(alignment: .leading) {
|
|
Text("↑ \(LibboxFormatBytes(connection.uploadTotal))")
|
|
Text("↓ \(LibboxFormatBytes(connection.downloadTotal))")
|
|
}
|
|
.font(.caption2)
|
|
VStack(alignment: .leading) {
|
|
Text(format(connection.createdAt))
|
|
Text(formatInterval(connection.createdAt, closedAt))
|
|
}
|
|
Spacer()
|
|
VStack(alignment: .trailing) {
|
|
Text(connection.inboundType + "/" + connection.inbound)
|
|
Text(connection.chain.reversed().joined(separator: "/"))
|
|
}
|
|
} else {
|
|
VStack(alignment: .leading) {
|
|
Text("↑ \(LibboxFormatBytes(connection.upload))/s")
|
|
Text("↓ \(LibboxFormatBytes(connection.download))/s")
|
|
}
|
|
.font(.caption2)
|
|
VStack(alignment: .leading) {
|
|
Text(LibboxFormatBytes(connection.uploadTotal))
|
|
Text(LibboxFormatBytes(connection.downloadTotal))
|
|
}
|
|
Spacer()
|
|
VStack(alignment: .trailing) {
|
|
Text(connection.inboundType + "/" + connection.inbound)
|
|
Text(connection.chain[0])
|
|
}
|
|
}
|
|
}
|
|
.font(.caption2.monospaced())
|
|
}
|
|
}
|
|
.foregroundColor(.textColor)
|
|
#if !os(tvOS)
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.contentShape(Rectangle())
|
|
#endif
|
|
}
|
|
#if !os(tvOS)
|
|
.buttonStyle(.plain)
|
|
.cardStyle()
|
|
#endif
|
|
.alert($alert)
|
|
.contextMenu {
|
|
if connection.closedAt == nil {
|
|
Button("Close", role: .destructive) {
|
|
Task {
|
|
await closeConnection()
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|
|
.background {
|
|
NavigationLink(isActive: $showDetails) {
|
|
ConnectionDetailsView(connection)
|
|
#if os(tvOS)
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .topBarLeading) {
|
|
BackButton()
|
|
}
|
|
}
|
|
#endif
|
|
} label: {
|
|
EmptyView()
|
|
}
|
|
.opacity(0)
|
|
}
|
|
}
|
|
|
|
private nonisolated func closeConnection() async {
|
|
do {
|
|
try await LibboxNewStandaloneCommandClient()!.closeConnection(connection.id)
|
|
} catch {
|
|
await MainActor.run {
|
|
alert = AlertState(action: "close connection", error: error)
|
|
}
|
|
}
|
|
}
|
|
}
|