mirror of
https://github.com/yggdrasil-network/yggdrasil-ios.git
synced 2025-04-28 14:15:09 +03:00
More updates
This commit is contained in:
parent
5fbb735f56
commit
0bea3a1613
9 changed files with 150 additions and 38 deletions
|
@ -112,6 +112,7 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
||||||
self.requestSummaryIPC()
|
self.requestSummaryIPC()
|
||||||
} else if conn.status == .disconnecting || conn.status == .disconnected {
|
} else if conn.status == .disconnecting || conn.status == .disconnected {
|
||||||
self.clearStatus()
|
self.clearStatus()
|
||||||
|
self.yggdrasilEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
||||||
|
|
||||||
guard let savedManagers else {
|
guard let savedManagers else {
|
||||||
print("Expected to find saved managers but didn't")
|
print("Expected to find saved managers but didn't")
|
||||||
self.yggdrasilSupported = false
|
// self.yggdrasilSupported = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,9 +187,7 @@ class CrossPlatformAppDelegate: PlatformAppDelegate, ObservableObject {
|
||||||
self.yggdrasilSubnet = summary.subnet
|
self.yggdrasilSubnet = summary.subnet
|
||||||
self.yggdrasilPublicKey = summary.publicKey
|
self.yggdrasilPublicKey = summary.publicKey
|
||||||
self.yggdrasilPeers = summary.peers
|
self.yggdrasilPeers = summary.peers
|
||||||
self.yggdrasilConnected = summary.peers.count > 0
|
self.yggdrasilConnected = summary.peers.filter { $0.up }.count > 0
|
||||||
|
|
||||||
print(self.yggdrasilPeers)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct YggdrasilSummary: Codable {
|
struct YggdrasilSummary: Codable {
|
||||||
var address: String
|
var address: String
|
||||||
|
@ -17,14 +18,18 @@ struct YggdrasilSummary: Codable {
|
||||||
func list() -> [String] {
|
func list() -> [String] {
|
||||||
return peers.map { $0.remote }
|
return peers.map { $0.remote }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listUp() -> [String] {
|
||||||
|
return peers.filter { $0.up }.map { $0.remote }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct YggdrasilPeer: Codable, Identifiable {
|
struct YggdrasilPeer: Codable, Identifiable {
|
||||||
var id: String { remote } // For Identifiable protocol
|
var id: String { remote } // For Identifiable protocol
|
||||||
let remote: String
|
let remote: String
|
||||||
let up: Bool
|
let up: Bool
|
||||||
let address: String
|
let address: String?
|
||||||
let key: String
|
let key: String?
|
||||||
let priority: UInt8
|
let priority: UInt8
|
||||||
let cost: UInt16?
|
let cost: UInt16?
|
||||||
|
|
||||||
|
@ -36,4 +41,11 @@ struct YggdrasilPeer: Codable, Identifiable {
|
||||||
case priority = "Priority"
|
case priority = "Priority"
|
||||||
case cost = "Cost"
|
case cost = "Cost"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getStatusBadgeColor() -> SwiftUI.Color {
|
||||||
|
if self.up {
|
||||||
|
return .green
|
||||||
|
}
|
||||||
|
return .gray
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,9 +113,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
subnet: self.yggdrasil.getSubnetString(),
|
subnet: self.yggdrasil.getSubnetString(),
|
||||||
publicKey: self.yggdrasil.getPublicKeyString(),
|
publicKey: self.yggdrasil.getPublicKeyString(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
peers: peers.sorted(by: { a, b in
|
peers: peers
|
||||||
a.remote < b.remote
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
if let json = try? JSONEncoder().encode(summary) {
|
if let json = try? JSONEncoder().encode(summary) {
|
||||||
completionHandler?(json)
|
completionHandler?(json)
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
39B51A492997062E0059D29D /* PeersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeersView.swift; sourceTree = "<group>"; };
|
39B51A492997062E0059D29D /* PeersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeersView.swift; sourceTree = "<group>"; };
|
||||||
39BF9FC12A2E9E51000E7269 /* YggdrasilNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YggdrasilNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
|
39BF9FC12A2E9E51000E7269 /* YggdrasilNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YggdrasilNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
39CC924B221DEDCE004960DC /* IPCResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPCResponses.swift; sourceTree = "<group>"; };
|
39CC924B221DEDCE004960DC /* IPCResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPCResponses.swift; sourceTree = "<group>"; };
|
||||||
|
39DB9BEC2BCF28FA009BF2A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
39F0205B2996CD760093F603 /* YggdrasilSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YggdrasilSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
39F0205B2996CD760093F603 /* YggdrasilSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YggdrasilSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
39F0205D2996CD760093F603 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
39F0205D2996CD760093F603 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||||
39F0205F2996CD760093F603 /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
39F0205F2996CD760093F603 /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -129,6 +130,7 @@
|
||||||
39F0205C2996CD760093F603 /* YggdrasilSwiftUI */ = {
|
39F0205C2996CD760093F603 /* YggdrasilSwiftUI */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
39DB9BEC2BCF28FA009BF2A4 /* Info.plist */,
|
||||||
39F0205D2996CD760093F603 /* Application.swift */,
|
39F0205D2996CD760093F603 /* Application.swift */,
|
||||||
39F0205F2996CD760093F603 /* StatusView.swift */,
|
39F0205F2996CD760093F603 /* StatusView.swift */,
|
||||||
39F0206A2996CF260093F603 /* SettingsView.swift */,
|
39F0206A2996CF260093F603 /* SettingsView.swift */,
|
||||||
|
@ -348,7 +350,9 @@
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = YggdrasilSwiftUI/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Yggdrasil;
|
INFOPLIST_KEY_CFBundleDisplayName = Yggdrasil;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
|
@ -400,7 +404,9 @@
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = YggdrasilSwiftUI/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Yggdrasil;
|
INFOPLIST_KEY_CFBundleDisplayName = Yggdrasil;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1530"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "39F0205A2996CD760093F603"
|
||||||
|
BuildableName = "YggdrasilSwiftUI.app"
|
||||||
|
BlueprintName = "YggdrasilSwiftUI"
|
||||||
|
ReferencedContainer = "container:Yggdrasil Network.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "39F0205A2996CD760093F603"
|
||||||
|
BuildableName = "YggdrasilSwiftUI.app"
|
||||||
|
BlueprintName = "YggdrasilSwiftUI"
|
||||||
|
ReferencedContainer = "container:Yggdrasil Network.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "39F0205A2996CD760093F603"
|
||||||
|
BuildableName = "YggdrasilSwiftUI.app"
|
||||||
|
BlueprintName = "YggdrasilSwiftUI"
|
||||||
|
ReferencedContainer = "container:Yggdrasil Network.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
5
YggdrasilSwiftUI/Info.plist
Normal file
5
YggdrasilSwiftUI/Info.plist
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict/>
|
||||||
|
</plist>
|
|
@ -76,6 +76,7 @@ struct PeersView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextField("Multicast password", text: $appDelegate.yggdrasilConfig.multicastPassword)
|
TextField("Multicast password", text: $appDelegate.yggdrasilConfig.multicastPassword)
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
}, header: {
|
}, header: {
|
||||||
Text("Local connectivity")
|
Text("Local connectivity")
|
||||||
})
|
})
|
||||||
|
|
|
@ -34,6 +34,7 @@ struct SettingsView: View {
|
||||||
Text("Automatically start when connected to")
|
Text("Automatically start when connected to")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
Section(content: {
|
Section(content: {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Button("Import configuration") {
|
Button("Import configuration") {
|
||||||
|
@ -76,6 +77,7 @@ struct SettingsView: View {
|
||||||
}, header: {
|
}, header: {
|
||||||
Text("Configuration")
|
Text("Configuration")
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.navigationTitle("Settings")
|
.navigationTitle("Settings")
|
||||||
|
|
|
@ -22,13 +22,12 @@ struct StatusView: View {
|
||||||
private func getStatusBadgeColor() -> SwiftUI.Color {
|
private func getStatusBadgeColor() -> SwiftUI.Color {
|
||||||
if !appDelegate.yggdrasilSupported {
|
if !appDelegate.yggdrasilSupported {
|
||||||
return .gray
|
return .gray
|
||||||
} else if !appDelegate.yggdrasilEnabled {
|
} else if appDelegate.yggdrasilConnected {
|
||||||
return .gray
|
|
||||||
} else if !appDelegate.yggdrasilConnected {
|
|
||||||
return .yellow
|
|
||||||
} else {
|
|
||||||
return .green
|
return .green
|
||||||
|
} else if appDelegate.yggdrasilEnabled {
|
||||||
|
return .yellow
|
||||||
}
|
}
|
||||||
|
return .gray
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getStatusBadgeText() -> String {
|
private func getStatusBadgeText() -> String {
|
||||||
|
@ -39,7 +38,7 @@ struct StatusView: View {
|
||||||
} else if !appDelegate.yggdrasilConnected {
|
} else if !appDelegate.yggdrasilConnected {
|
||||||
return "No peers connected"
|
return "No peers connected"
|
||||||
} else {
|
} else {
|
||||||
return "Connected to \(appDelegate.yggdrasilPeers.count) peer(s)"
|
return "Connected to \(appDelegate.yggdrasilPeers.filter { $0.up }.count) peer(s)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +48,7 @@ struct StatusView: View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Toggle("Enable Yggdrasil", isOn: $appDelegate.yggdrasilEnabled)
|
Toggle("Enable Yggdrasil", isOn: $appDelegate.yggdrasilEnabled)
|
||||||
.disabled(!appDelegate.yggdrasilSupported)
|
.disabled(!appDelegate.yggdrasilSupported)
|
||||||
|
.padding(.bottom, 2)
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "circlebadge.fill")
|
Image(systemName: "circlebadge.fill")
|
||||||
.foregroundColor(statusBadgeColor)
|
.foregroundColor(statusBadgeColor)
|
||||||
|
@ -116,15 +116,6 @@ struct StatusView: View {
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
}
|
}
|
||||||
/*HStack {
|
|
||||||
Text("Coordinates")
|
|
||||||
Spacer()
|
|
||||||
Text(appDelegate.yggdrasilCoords)
|
|
||||||
.foregroundColor(Color.gray)
|
|
||||||
.truncationMode(.tail)
|
|
||||||
.lineLimit(1)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
}*/
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Public Key")
|
Text("Public Key")
|
||||||
Spacer()
|
Spacer()
|
||||||
|
@ -139,10 +130,20 @@ struct StatusView: View {
|
||||||
Text("Details")
|
Text("Details")
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.appDelegate.yggdrasilEnabled {
|
|
||||||
Section(content: {
|
Section(content: {
|
||||||
|
if self.appDelegate.yggdrasilPeers.count == 0 {
|
||||||
|
Text("No peers are connected")
|
||||||
|
.foregroundStyle(.tertiary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
} else {
|
||||||
List(self.appDelegate.yggdrasilPeers.sorted(by: { a, b in
|
List(self.appDelegate.yggdrasilPeers.sorted(by: { a, b in
|
||||||
a.key < a.key
|
if a.up && !b.up {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !a.up && b.up {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return a.remote < b.remote
|
||||||
}), id: \.remote) { peer in
|
}), id: \.remote) { peer in
|
||||||
VStack {
|
VStack {
|
||||||
Text(peer.remote)
|
Text(peer.remote)
|
||||||
|
@ -150,21 +151,31 @@ struct StatusView: View {
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
Text(peer.address)
|
.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")
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
.font(.system(size: 11, design: .monospaced))
|
.font(.system(size: 11))
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.padding(.all, 2)
|
.padding(.all, 2)
|
||||||
|
.padding(.top, 4)
|
||||||
|
.padding(.bottom, 4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, header: {
|
}, header: {
|
||||||
Text("Peers")
|
Text("Peers")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.navigationTitle("Yggdrasil")
|
.navigationTitle("Yggdrasil")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue