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
|
@ -12,11 +12,11 @@ import NetworkExtension
|
|||
struct Application: App {
|
||||
@State private var selection: String? = "Status"
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
@UIApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||
#elseif os(macOS)
|
||||
#elseif os(macOS)
|
||||
@NSApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
|
@ -53,14 +53,14 @@ struct Application: App {
|
|||
//.listStyle(.sidebar)
|
||||
//.navigationSplitViewColumnWidth(200)
|
||||
|
||||
Image("YggdrasilLogo")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.primary)
|
||||
.opacity(0.1)
|
||||
.frame(maxWidth: 200, alignment: .bottom)
|
||||
.padding(.all, 24)
|
||||
/*Image("YggdrasilLogo")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.primary)
|
||||
.opacity(0.05)
|
||||
.frame(maxWidth: 200, alignment: .bottom)
|
||||
.padding(.all, 24)*/
|
||||
}
|
||||
.navigationSplitViewColumnWidth(200)
|
||||
.listStyle(.sidebar)
|
||||
|
@ -82,8 +82,8 @@ struct Application: App {
|
|||
}
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ struct PeersView: View {
|
|||
Section(content: {
|
||||
ForEach(Array(appDelegate.yggdrasilConfig.peers.enumerated()), id: \.offset) { index, peer in
|
||||
HStack() {
|
||||
TextField("Peer", text: $appDelegate.yggdrasilConfig.peers[index])
|
||||
TextField("tls://host:port", text: $appDelegate.yggdrasilConfig.peers[index])
|
||||
.labelsHidden()
|
||||
#if os(iOS)
|
||||
.disabled(!editMode!.wrappedValue.isEditing)
|
||||
|
@ -75,8 +75,18 @@ struct PeersView: View {
|
|||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
TextField("Multicast password", text: $appDelegate.yggdrasilConfig.multicastPassword)
|
||||
.labelStyle(.titleAndIcon)
|
||||
VStack {
|
||||
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: {
|
||||
Text("Local connectivity")
|
||||
})
|
||||
|
@ -85,8 +95,10 @@ struct PeersView: View {
|
|||
.navigationTitle("Peers")
|
||||
#if os(iOS)
|
||||
.toolbar {
|
||||
Button("Add", systemImage: "plus") {
|
||||
appDelegate.yggdrasilConfig.peers.append("")
|
||||
if editMode!.wrappedValue.isEditing {
|
||||
Button("Add", systemImage: "plus") {
|
||||
appDelegate.yggdrasilConfig.peers.append("")
|
||||
}
|
||||
}
|
||||
EditButton()
|
||||
.onChange(of: editMode!.wrappedValue) { edit in
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
//@Binding public var yggdrasilConfiguration: ConfigurationProxy
|
||||
@ObservedObject private var appDelegate = Application.appDelegate
|
||||
|
||||
@State private var deviceName = ""
|
||||
|
@ -35,55 +34,55 @@ struct SettingsView: View {
|
|||
})
|
||||
|
||||
/*
|
||||
Section(content: {
|
||||
VStack(alignment: .leading) {
|
||||
Button("Import configuration") {
|
||||
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Import configuration from another device, including the public key and Yggdrasil IP address.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Button("Export configuration") {
|
||||
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Configuration will be exported as a file. Your configuration contains your private key which is extremely sensitive. Do not share it with anyone.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Button("Reset configuration") {
|
||||
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
.foregroundColor(.red)
|
||||
Text("Resetting will overwrite with newly generated configuration. Your public key and Yggdrasil IP address will change.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}, header: {
|
||||
Text("Configuration")
|
||||
})
|
||||
Section(content: {
|
||||
VStack(alignment: .leading) {
|
||||
Button("Import configuration") {
|
||||
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Import configuration from another device, including the public key and Yggdrasil IP address.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Button("Export configuration") {
|
||||
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Configuration will be exported as a file. Your configuration contains your private key which is extremely sensitive. Do not share it with anyone.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Button("Reset configuration") {
|
||||
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
.foregroundColor(.red)
|
||||
Text("Resetting will overwrite with newly generated configuration. Your public key and Yggdrasil IP address will change.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}, header: {
|
||||
Text("Configuration")
|
||||
})
|
||||
*/
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Settings")
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreImage.CIFilterBuiltins
|
||||
|
||||
#if os(iOS)
|
||||
typealias MyListStyle = DefaultListStyle
|
||||
|
@ -18,16 +19,19 @@ struct StatusView: View {
|
|||
|
||||
@State private var statusBadgeColor: SwiftUI.Color = .gray
|
||||
@State private var statusBadgeText: String = "Not enabled"
|
||||
@State private var showingPublicKeyPopover = false
|
||||
|
||||
let context = CIContext()
|
||||
let filter = CIFilter.qrCodeGenerator()
|
||||
|
||||
private func getStatusBadgeColor() -> SwiftUI.Color {
|
||||
if !appDelegate.yggdrasilSupported {
|
||||
return .gray
|
||||
} else if appDelegate.yggdrasilConnected {
|
||||
if appDelegate.yggdrasilConnected {
|
||||
return .green
|
||||
} else if appDelegate.yggdrasilEnabled {
|
||||
return .yellow
|
||||
} else {
|
||||
return .gray
|
||||
}
|
||||
return .gray
|
||||
}
|
||||
|
||||
private func getStatusBadgeText() -> String {
|
||||
|
@ -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 {
|
||||
Form {
|
||||
Section(content: {
|
||||
|
@ -125,6 +163,25 @@ struct StatusView: View {
|
|||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
.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: {
|
||||
Text("Details")
|
||||
|
@ -146,25 +203,48 @@ struct StatusView: View {
|
|||
return a.remote < b.remote
|
||||
}), id: \.remote) { peer in
|
||||
VStack {
|
||||
Text(peer.remote)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
.textSelection(.enabled)
|
||||
.padding(.bottom, 2)
|
||||
HStack {
|
||||
Text(peer.remote)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
.textSelection(.enabled)
|
||||
.padding(.bottom, 2)
|
||||
}
|
||||
HStack {
|
||||
Image(systemName: "circlebadge.fill")
|
||||
.foregroundColor(peer.getStatusBadgeColor())
|
||||
.onChange(of: peer.up) { newValue in
|
||||
statusBadgeColor = peer.getStatusBadgeColor()
|
||||
}
|
||||
Text(peer.up ? peer.address ?? "Unknown IP address" : "Not connected")
|
||||
Text(peer.up ? "Connected" : "Not connected")
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundColor(Color.gray)
|
||||
.font(.system(size: 11))
|
||||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
.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)
|
||||
|
@ -178,9 +258,9 @@ struct StatusView: View {
|
|||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Yggdrasil")
|
||||
#if os(iOS)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue