mirror of
https://github.com/yggdrasil-network/yggdrasil-ios.git
synced 2025-04-28 14:15:09 +03:00
Tweaks
This commit is contained in:
parent
0bea3a1613
commit
1b9e84d056
9 changed files with 234 additions and 137 deletions
|
@ -32,9 +32,14 @@ class PlatformItemSource: NSObject {}
|
||||||
|
|
||||||
class ConfigurationProxy: PlatformItemSource {
|
class ConfigurationProxy: PlatformItemSource {
|
||||||
private var manager: NETunnelProviderManager?
|
private var manager: NETunnelProviderManager?
|
||||||
private var json: Data? = nil
|
private var json: Data? = nil {
|
||||||
|
didSet {
|
||||||
|
summary = MobileSummaryFromConfig(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
private var dict: [String: Any]? = [:]
|
private var dict: [String: Any]? = [:]
|
||||||
private var timer: Timer?
|
private var timer: Timer?
|
||||||
|
public var summary: MobileParsedConfig?
|
||||||
|
|
||||||
init(manager: NETunnelProviderManager) {
|
init(manager: NETunnelProviderManager) {
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
|
@ -46,11 +51,11 @@ class ConfigurationProxy: PlatformItemSource {
|
||||||
} catch {
|
} catch {
|
||||||
NSLog("ConfigurationProxy: Error deserialising JSON (\(error))")
|
NSLog("ConfigurationProxy: Error deserialising JSON (\(error))")
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
self.set("name", inSection: "NodeInfo", to: UIDevice.current.name)
|
self.set("name", inSection: "NodeInfo", to: UIDevice.current.name)
|
||||||
#elseif os(OSX)
|
#elseif os(OSX)
|
||||||
self.set("name", inSection: "NodeInfo", to: Host.current().localizedName ?? "")
|
self.set("name", inSection: "NodeInfo", to: Host.current().localizedName ?? "")
|
||||||
#endif
|
#endif
|
||||||
self.fix()
|
self.fix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,25 +308,25 @@ class ConfigurationProxy: PlatformItemSource {
|
||||||
wifirule.interfaceTypeMatch = .any
|
wifirule.interfaceTypeMatch = .any
|
||||||
rules.insert(wifirule, at: 0)
|
rules.insert(wifirule, at: 0)
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
if self.get("Ethernet", inSection: "AutoStart") as? Bool ?? false {
|
if self.get("Ethernet", inSection: "AutoStart") as? Bool ?? false {
|
||||||
let wifirule = NEOnDemandRuleConnect()
|
let wifirule = NEOnDemandRuleConnect()
|
||||||
wifirule.interfaceTypeMatch = .ethernet
|
wifirule.interfaceTypeMatch = .ethernet
|
||||||
rules.insert(wifirule, at: 0)
|
rules.insert(wifirule, at: 0)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if self.get("WiFi", inSection: "AutoStart") as? Bool ?? false {
|
if self.get("WiFi", inSection: "AutoStart") as? Bool ?? false {
|
||||||
let wifirule = NEOnDemandRuleConnect()
|
let wifirule = NEOnDemandRuleConnect()
|
||||||
wifirule.interfaceTypeMatch = .wiFi
|
wifirule.interfaceTypeMatch = .wiFi
|
||||||
rules.insert(wifirule, at: 0)
|
rules.insert(wifirule, at: 0)
|
||||||
}
|
}
|
||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
if self.get("Mobile", inSection: "AutoStart") as? Bool ?? false {
|
if self.get("Mobile", inSection: "AutoStart") as? Bool ?? false {
|
||||||
let mobilerule = NEOnDemandRuleConnect()
|
let mobilerule = NEOnDemandRuleConnect()
|
||||||
mobilerule.interfaceTypeMatch = .cellular
|
mobilerule.interfaceTypeMatch = .cellular
|
||||||
rules.insert(mobilerule, at: 0)
|
rules.insert(mobilerule, at: 0)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
manager.onDemandRules = rules
|
manager.onDemandRules = rules
|
||||||
manager.isOnDemandEnabled = rules.count > 1
|
manager.isOnDemandEnabled = rules.count > 1
|
||||||
providerProtocol.disconnectOnSleep = rules.count > 1
|
providerProtocol.disconnectOnSleep = rules.count > 1
|
||||||
|
@ -346,7 +351,7 @@ class ConfigurationProxy: PlatformItemSource {
|
||||||
self.json = try JSONSerialization.data(withJSONObject: self.dict as Any, options: .prettyPrinted)
|
self.json = try JSONSerialization.data(withJSONObject: self.dict as Any, options: .prettyPrinted)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
override func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
override func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||||
return "yggdrasil.conf"
|
return "yggdrasil.conf"
|
||||||
}
|
}
|
||||||
|
@ -361,5 +366,5 @@ class ConfigurationProxy: PlatformItemSource {
|
||||||
}
|
}
|
||||||
return "yggdrasil.conf.json"
|
return "yggdrasil.conf.json"
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,11 +108,12 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
||||||
func becameBackground() {}
|
func becameBackground() {}
|
||||||
|
|
||||||
func updateStatus(conn: NEVPNConnection) {
|
func updateStatus(conn: NEVPNConnection) {
|
||||||
if conn.status == .connected {
|
if conn.status == .connected || conn.status == .connecting {
|
||||||
|
self.yggdrasilEnabled = true
|
||||||
self.requestSummaryIPC()
|
self.requestSummaryIPC()
|
||||||
} else if conn.status == .disconnecting || conn.status == .disconnected {
|
} else if conn.status == .disconnecting || conn.status == .disconnected {
|
||||||
self.clearStatus()
|
|
||||||
self.yggdrasilEnabled = false
|
self.yggdrasilEnabled = false
|
||||||
|
self.clearStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +189,8 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
||||||
self.yggdrasilPublicKey = summary.publicKey
|
self.yggdrasilPublicKey = summary.publicKey
|
||||||
self.yggdrasilPeers = summary.peers
|
self.yggdrasilPeers = summary.peers
|
||||||
self.yggdrasilConnected = summary.peers.filter { $0.up }.count > 0
|
self.yggdrasilConnected = summary.peers.filter { $0.up }.count > 0
|
||||||
|
|
||||||
|
print("Response: \(String(data: js, encoding: .utf8))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,9 @@ struct YggdrasilPeer: Codable, Identifiable {
|
||||||
let key: String?
|
let key: String?
|
||||||
let priority: UInt8
|
let priority: UInt8
|
||||||
let cost: UInt16?
|
let cost: UInt16?
|
||||||
|
let rxBytes: Double?
|
||||||
|
let txBytes: Double?
|
||||||
|
let uptime: Int64?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case remote = "URI"
|
case remote = "URI"
|
||||||
|
@ -40,6 +43,9 @@ struct YggdrasilPeer: Codable, Identifiable {
|
||||||
case key = "Key"
|
case key = "Key"
|
||||||
case priority = "Priority"
|
case priority = "Priority"
|
||||||
case cost = "Cost"
|
case cost = "Cost"
|
||||||
|
case rxBytes = "RXBytes"
|
||||||
|
case txBytes = "TXBytes"
|
||||||
|
case uptime = "Uptime"
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getStatusBadgeColor() -> SwiftUI.Color {
|
public func getStatusBadgeColor() -> SwiftUI.Color {
|
||||||
|
|
|
@ -99,6 +99,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
switch request {
|
switch request {
|
||||||
case "summary":
|
case "summary":
|
||||||
let pj = self.yggdrasil.getPeersJSON()
|
let pj = self.yggdrasil.getPeersJSON()
|
||||||
|
NSLog("JSON: \(pj)")
|
||||||
var peers: [YggdrasilPeer] = []
|
var peers: [YggdrasilPeer] = []
|
||||||
do {
|
do {
|
||||||
peers = try JSONDecoder().decode(
|
peers = try JSONDecoder().decode(
|
||||||
|
|
|
@ -5,34 +5,8 @@
|
||||||
<key>AvailableLibraries</key>
|
<key>AvailableLibraries</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>LibraryIdentifier</key>
|
<key>BinaryPath</key>
|
||||||
<string>macos-arm64_x86_64</string>
|
<string>Yggdrasil.framework/Versions/A/Yggdrasil</string>
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>Yggdrasil.framework</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
<string>x86_64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>macos</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64_x86_64-maccatalyst</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>Yggdrasil.framework</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
<string>x86_64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
<key>SupportedPlatformVariant</key>
|
|
||||||
<string>maccatalyst</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
<key>LibraryIdentifier</key>
|
||||||
<string>ios-arm64</string>
|
<string>ios-arm64</string>
|
||||||
<key>LibraryPath</key>
|
<key>LibraryPath</key>
|
||||||
|
@ -45,6 +19,8 @@
|
||||||
<string>ios</string>
|
<string>ios</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>BinaryPath</key>
|
||||||
|
<string>Yggdrasil.framework/Versions/A/Yggdrasil</string>
|
||||||
<key>LibraryIdentifier</key>
|
<key>LibraryIdentifier</key>
|
||||||
<string>ios-arm64_x86_64-simulator</string>
|
<string>ios-arm64_x86_64-simulator</string>
|
||||||
<key>LibraryPath</key>
|
<key>LibraryPath</key>
|
||||||
|
@ -59,6 +35,21 @@
|
||||||
<key>SupportedPlatformVariant</key>
|
<key>SupportedPlatformVariant</key>
|
||||||
<string>simulator</string>
|
<string>simulator</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>BinaryPath</key>
|
||||||
|
<string>Yggdrasil.framework/Versions/A/Yggdrasil</string>
|
||||||
|
<key>LibraryIdentifier</key>
|
||||||
|
<string>macos-arm64_x86_64</string>
|
||||||
|
<key>LibraryPath</key>
|
||||||
|
<string>Yggdrasil.framework</string>
|
||||||
|
<key>SupportedArchitectures</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
<string>x86_64</string>
|
||||||
|
</array>
|
||||||
|
<key>SupportedPlatform</key>
|
||||||
|
<string>macos</string>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XFWK</string>
|
<string>XFWK</string>
|
||||||
|
|
|
@ -12,11 +12,11 @@ import NetworkExtension
|
||||||
struct Application: App {
|
struct Application: App {
|
||||||
@State private var selection: String? = "Status"
|
@State private var selection: String? = "Status"
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@UIApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
@UIApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
@NSApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
@NSApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) var scenePhase
|
||||||
|
|
||||||
|
@ -53,14 +53,14 @@ struct Application: App {
|
||||||
//.listStyle(.sidebar)
|
//.listStyle(.sidebar)
|
||||||
//.navigationSplitViewColumnWidth(200)
|
//.navigationSplitViewColumnWidth(200)
|
||||||
|
|
||||||
Image("YggdrasilLogo")
|
/*Image("YggdrasilLogo")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
.opacity(0.1)
|
.opacity(0.05)
|
||||||
.frame(maxWidth: 200, alignment: .bottom)
|
.frame(maxWidth: 200, alignment: .bottom)
|
||||||
.padding(.all, 24)
|
.padding(.all, 24)*/
|
||||||
}
|
}
|
||||||
.navigationSplitViewColumnWidth(200)
|
.navigationSplitViewColumnWidth(200)
|
||||||
.listStyle(.sidebar)
|
.listStyle(.sidebar)
|
||||||
|
@ -82,8 +82,8 @@ struct Application: App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.windowStyle(.hiddenTitleBar)
|
.windowStyle(.hiddenTitleBar)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct PeersView: View {
|
||||||
Section(content: {
|
Section(content: {
|
||||||
ForEach(Array(appDelegate.yggdrasilConfig.peers.enumerated()), id: \.offset) { index, peer in
|
ForEach(Array(appDelegate.yggdrasilConfig.peers.enumerated()), id: \.offset) { index, peer in
|
||||||
HStack() {
|
HStack() {
|
||||||
TextField("Peer", text: $appDelegate.yggdrasilConfig.peers[index])
|
TextField("tls://host:port", text: $appDelegate.yggdrasilConfig.peers[index])
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.disabled(!editMode!.wrappedValue.isEditing)
|
.disabled(!editMode!.wrappedValue.isEditing)
|
||||||
|
@ -75,8 +75,18 @@ struct PeersView: View {
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextField("Multicast password", text: $appDelegate.yggdrasilConfig.multicastPassword)
|
VStack {
|
||||||
.labelStyle(.titleAndIcon)
|
HStack {
|
||||||
|
Text("Multicast password")
|
||||||
|
TextField("None", text: $appDelegate.yggdrasilConfig.multicastPassword)
|
||||||
|
.labelsHidden()
|
||||||
|
.multilineTextAlignment(.trailing)
|
||||||
|
}
|
||||||
|
Text("If provided, this device will only automatically peer with other nodes that share the same password.")
|
||||||
|
.font(.system(size: 11))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
}, header: {
|
}, header: {
|
||||||
Text("Local connectivity")
|
Text("Local connectivity")
|
||||||
})
|
})
|
||||||
|
@ -85,9 +95,11 @@ struct PeersView: View {
|
||||||
.navigationTitle("Peers")
|
.navigationTitle("Peers")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
if editMode!.wrappedValue.isEditing {
|
||||||
Button("Add", systemImage: "plus") {
|
Button("Add", systemImage: "plus") {
|
||||||
appDelegate.yggdrasilConfig.peers.append("")
|
appDelegate.yggdrasilConfig.peers.append("")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
EditButton()
|
EditButton()
|
||||||
.onChange(of: editMode!.wrappedValue) { edit in
|
.onChange(of: editMode!.wrappedValue) { edit in
|
||||||
if !edit.isEditing {
|
if !edit.isEditing {
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
//@Binding public var yggdrasilConfiguration: ConfigurationProxy
|
|
||||||
@ObservedObject private var appDelegate = Application.appDelegate
|
@ObservedObject private var appDelegate = Application.appDelegate
|
||||||
|
|
||||||
@State private var deviceName = ""
|
@State private var deviceName = ""
|
||||||
|
@ -81,9 +80,9 @@ struct SettingsView: View {
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.navigationTitle("Settings")
|
.navigationTitle("Settings")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.large)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import CoreImage.CIFilterBuiltins
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
typealias MyListStyle = DefaultListStyle
|
typealias MyListStyle = DefaultListStyle
|
||||||
|
@ -18,17 +19,20 @@ struct StatusView: View {
|
||||||
|
|
||||||
@State private var statusBadgeColor: SwiftUI.Color = .gray
|
@State private var statusBadgeColor: SwiftUI.Color = .gray
|
||||||
@State private var statusBadgeText: String = "Not enabled"
|
@State private var statusBadgeText: String = "Not enabled"
|
||||||
|
@State private var showingPublicKeyPopover = false
|
||||||
|
|
||||||
|
let context = CIContext()
|
||||||
|
let filter = CIFilter.qrCodeGenerator()
|
||||||
|
|
||||||
private func getStatusBadgeColor() -> SwiftUI.Color {
|
private func getStatusBadgeColor() -> SwiftUI.Color {
|
||||||
if !appDelegate.yggdrasilSupported {
|
if appDelegate.yggdrasilConnected {
|
||||||
return .gray
|
|
||||||
} else if appDelegate.yggdrasilConnected {
|
|
||||||
return .green
|
return .green
|
||||||
} else if appDelegate.yggdrasilEnabled {
|
} else if appDelegate.yggdrasilEnabled {
|
||||||
return .yellow
|
return .yellow
|
||||||
}
|
} else {
|
||||||
return .gray
|
return .gray
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func getStatusBadgeText() -> String {
|
private func getStatusBadgeText() -> String {
|
||||||
if !appDelegate.yggdrasilSupported {
|
if !appDelegate.yggdrasilSupported {
|
||||||
|
@ -42,6 +46,40 @@ struct StatusView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatBytes(bytes: Double) -> String {
|
||||||
|
guard bytes > 0 else {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from http://stackoverflow.com/a/18650828
|
||||||
|
let suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||||
|
let k: Double = 1024
|
||||||
|
let i = floor(log(bytes) / log(k))
|
||||||
|
|
||||||
|
// Format number with thousands separator and everything below 1 GB with no decimal places.
|
||||||
|
let numberFormatter = NumberFormatter()
|
||||||
|
numberFormatter.maximumFractionDigits = i < 3 ? 0 : 1
|
||||||
|
numberFormatter.numberStyle = .decimal
|
||||||
|
|
||||||
|
let numberString = numberFormatter.string(from: NSNumber(value: bytes / pow(k, i))) ?? "Unknown"
|
||||||
|
let suffix = suffixes[Int(i)]
|
||||||
|
return "\(numberString) \(suffix)"
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
func generateQRCode(from string: String) -> UIImage {
|
||||||
|
filter.message = Data(string.utf8)
|
||||||
|
|
||||||
|
if let outputImage = filter.outputImage {
|
||||||
|
if let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
|
||||||
|
return UIImage(cgImage: cgImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UIImage(systemName: "xmark.circle") ?? UIImage()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section(content: {
|
Section(content: {
|
||||||
|
@ -125,6 +163,25 @@ struct StatusView: View {
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
|
#if os(iOS)
|
||||||
|
if appDelegate.yggdrasilPublicKey != "N/A" {
|
||||||
|
Button("QR Code", systemImage: "qrcode", action: {
|
||||||
|
showingPublicKeyPopover = true
|
||||||
|
})
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.popover(isPresented: $showingPublicKeyPopover) {
|
||||||
|
Text("Public Key")
|
||||||
|
.font(.headline)
|
||||||
|
.padding()
|
||||||
|
Image(uiImage: generateQRCode(from: "\(appDelegate.yggdrasilPublicKey)"))
|
||||||
|
.interpolation(.none)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 200, height: 200)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}, header: {
|
}, header: {
|
||||||
Text("Details")
|
Text("Details")
|
||||||
|
@ -146,25 +203,48 @@ struct StatusView: View {
|
||||||
return a.remote < b.remote
|
return a.remote < b.remote
|
||||||
}), id: \.remote) { peer in
|
}), id: \.remote) { peer in
|
||||||
VStack {
|
VStack {
|
||||||
|
HStack {
|
||||||
Text(peer.remote)
|
Text(peer.remote)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
.padding(.bottom, 2)
|
.padding(.bottom, 2)
|
||||||
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "circlebadge.fill")
|
Image(systemName: "circlebadge.fill")
|
||||||
.foregroundColor(peer.getStatusBadgeColor())
|
.foregroundColor(peer.getStatusBadgeColor())
|
||||||
.onChange(of: peer.up) { newValue in
|
.onChange(of: peer.up) { newValue in
|
||||||
statusBadgeColor = peer.getStatusBadgeColor()
|
statusBadgeColor = peer.getStatusBadgeColor()
|
||||||
}
|
}
|
||||||
Text(peer.up ? peer.address ?? "Unknown IP address" : "Not connected")
|
Text(peer.up ? "Connected" : "Not connected")
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
.font(.system(size: 11))
|
.font(.system(size: 11))
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
|
if peer.up {
|
||||||
|
Spacer()
|
||||||
|
if let uptime = peer.uptime {
|
||||||
|
Label(Duration(secondsComponent: uptime/1000000000, attosecondsComponent: 0).formatted(), systemImage: "clock")
|
||||||
|
.font(.system(size: 11))
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
if let rxBytes = peer.rxBytes {
|
||||||
|
Label(formatBytes(bytes: rxBytes), systemImage: "arrowshape.down")
|
||||||
|
.font(.system(size: 11))
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
|
.foregroundStyle(.teal)
|
||||||
|
}
|
||||||
|
if let txBytes = peer.txBytes {
|
||||||
|
Label(formatBytes(bytes: txBytes), systemImage: "arrowshape.up")
|
||||||
|
.font(.system(size: 11))
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
|
.foregroundStyle(.purple)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.all, 2)
|
.padding(.all, 2)
|
||||||
|
@ -178,9 +258,9 @@ struct StatusView: View {
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.navigationTitle("Yggdrasil")
|
.navigationTitle("Yggdrasil")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.large)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue