Some SwiftUI
63
YggdrasilSwiftUI/Application.swift
Normal file
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// YggdrasilSwiftUIApp.swift
|
||||
// YggdrasilSwiftUI
|
||||
//
|
||||
// Created by Neil on 10/02/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import NetworkExtension
|
||||
|
||||
@main
|
||||
struct Application: App {
|
||||
#if os(iOS)
|
||||
@UIApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||
#elseif os(macOS)
|
||||
@NSApplicationDelegateAdaptor(CrossPlatformAppDelegate.self) static var appDelegate: CrossPlatformAppDelegate
|
||||
#endif
|
||||
|
||||
@State private var selection: String? = "Status"
|
||||
@State private var config: ConfigurationProxy = ConfigurationProxy()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
NavigationSplitView {
|
||||
List(selection: $selection) {
|
||||
NavigationLink(destination: StatusView(yggdrasilConfiguration: $config)) {
|
||||
HStack {
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(minWidth: 24)
|
||||
Text("Status")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: PeersView()) {
|
||||
HStack {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(minWidth: 24)
|
||||
Text("Peers")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: SettingsView()) {
|
||||
HStack {
|
||||
Image(systemName: "gear")
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(minWidth: 24)
|
||||
Text("Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.sidebar)
|
||||
.navigationSplitViewColumnWidth(200)
|
||||
} detail: {
|
||||
StatusView(yggdrasilConfiguration: $config)
|
||||
}
|
||||
.navigationTitle("Yggdrasil")
|
||||
.navigationSplitViewStyle(.automatic)
|
||||
}
|
||||
#if os(macOS)
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "drawing copy-1.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "drawing copy.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "drawing copy-2.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "drawing copy-3.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "drawing copy-5.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "drawing copy-4.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4 KiB |
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "drawing copy-4 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "drawing copy-4.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 26 KiB |
6
YggdrasilSwiftUI/Assets.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
77
YggdrasilSwiftUI/PeersView.swift
Normal file
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// StatisticsView.swift
|
||||
// YggdrasilSwiftUI
|
||||
//
|
||||
// Created by Neil on 10/02/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PeersView: View {
|
||||
@State private var peers = ["Paul", "Taylor", "Adele"]
|
||||
|
||||
@State private var multicastAdvertise = false
|
||||
@State private var multicastListen = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(content: {
|
||||
ForEach(peers, id: \.self) { peer in
|
||||
Text(peer)
|
||||
}
|
||||
.onDelete(perform: delete)
|
||||
Text("Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer. Data charges may apply when using mobile data.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}, header: {
|
||||
Text("Configured peers")
|
||||
})
|
||||
|
||||
Section(content: {
|
||||
Toggle(isOn: $multicastAdvertise) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Discoverable over multicast")
|
||||
Text("Make your device discoverable to other Yggdrasil nodes on the same Wi-Fi network.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
Toggle(isOn: $multicastListen) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Search for multicast peers")
|
||||
Text("Automatically connect to discoverable Yggdrasil nodes on the same Wi-Fi network.")
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}, header: {
|
||||
Text("Local connectivity")
|
||||
})
|
||||
}
|
||||
#if os(iOS)
|
||||
.toolbar {
|
||||
Button(role: nil, action: {
|
||||
|
||||
}, label: {
|
||||
Image(systemName: "plus")
|
||||
})
|
||||
EditButton()
|
||||
}
|
||||
#endif
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Peers")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
#endif
|
||||
}
|
||||
|
||||
func delete(at offsets: IndexSet) {
|
||||
peers.remove(atOffsets: offsets)
|
||||
}
|
||||
}
|
||||
|
||||
struct PeersView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PeersView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
84
YggdrasilSwiftUI/SettingsView.swift
Normal file
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// SettingsView.swift
|
||||
// YggdrasilSwiftUI
|
||||
//
|
||||
// Created by Neil on 10/02/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
@State private var deviceName = ""
|
||||
|
||||
@State private var autoStartOnWiFi = false
|
||||
@State private var autoStartOnCellular = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(content: {
|
||||
TextField("Device Name", text: $deviceName)
|
||||
}, header: {
|
||||
Text("Public Identity")
|
||||
})
|
||||
|
||||
Section(content: {
|
||||
Toggle("Wi-Fi", isOn: $autoStartOnWiFi)
|
||||
Toggle("Mobile Network", isOn: $autoStartOnCellular)
|
||||
}, header: {
|
||||
Text("Automatically start when connected to")
|
||||
})
|
||||
|
||||
Section(content: {
|
||||
VStack(alignment: .leading) {
|
||||
Button("Import configuration") {
|
||||
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
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
|
||||
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)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView()
|
||||
}
|
||||
}
|
125
YggdrasilSwiftUI/StatusView.swift
Normal file
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// ContentView.swift
|
||||
// YggdrasilSwiftUI
|
||||
//
|
||||
// Created by Neil on 10/02/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
#if os(iOS)
|
||||
typealias MyListStyle = DefaultListStyle
|
||||
#else
|
||||
typealias MyListStyle = SidebarListStyle
|
||||
#endif
|
||||
|
||||
struct StatusView: View {
|
||||
@Binding public var yggdrasilConfiguration: ConfigurationProxy
|
||||
|
||||
@ObservedObject private var appDelegate = Application.appDelegate
|
||||
|
||||
@State private var statusBadgeColor: SwiftUI.Color = .gray
|
||||
@State private var statusBadgeText: String = "Not enabled"
|
||||
|
||||
private func getStatusBadgeColor() -> SwiftUI.Color {
|
||||
if !appDelegate.yggdrasilEnabled {
|
||||
return .gray
|
||||
} else if !appDelegate.yggdrasilConnected {
|
||||
return .yellow
|
||||
} else {
|
||||
return .green
|
||||
}
|
||||
}
|
||||
|
||||
private func getStatusBadgeText() -> String {
|
||||
if !appDelegate.yggdrasilEnabled {
|
||||
return "Not enabled"
|
||||
} else if !appDelegate.yggdrasilConnected {
|
||||
return "Not connected"
|
||||
} else {
|
||||
return "Connected"
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(content: {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle("Enable Yggdrasil", isOn: $appDelegate.yggdrasilEnabled)
|
||||
.onTapGesture {
|
||||
appDelegate.toggleYggdrasil()
|
||||
}
|
||||
HStack {
|
||||
Image(systemName: "circlebadge.fill")
|
||||
.foregroundColor(statusBadgeColor)
|
||||
.onAppear(perform: {
|
||||
statusBadgeColor = getStatusBadgeColor()
|
||||
})
|
||||
.onChange(of: appDelegate.yggdrasilEnabled) { newValue in
|
||||
statusBadgeColor = getStatusBadgeColor()
|
||||
}
|
||||
Text(statusBadgeText)
|
||||
.foregroundColor(.gray)
|
||||
.font(.system(size: 11))
|
||||
.onAppear(perform: {
|
||||
statusBadgeText = getStatusBadgeText()
|
||||
})
|
||||
.onChange(of: appDelegate.yggdrasilEnabled) { newValue in
|
||||
statusBadgeText = getStatusBadgeText()
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text("Version")
|
||||
Spacer()
|
||||
Text(appDelegate.yggdrasilVersion())
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
})
|
||||
|
||||
Section(content: {
|
||||
HStack {
|
||||
Text("IP")
|
||||
Spacer()
|
||||
Text(appDelegate.yggdrasilIP)
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
HStack {
|
||||
Text("Subnet")
|
||||
Spacer()
|
||||
Text(appDelegate.yggdrasilSubnet)
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
HStack {
|
||||
Text("Coordinates")
|
||||
Spacer()
|
||||
Text(appDelegate.yggdrasilCoords)
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
/*
|
||||
HStack {
|
||||
Text("Public Key")
|
||||
Spacer()
|
||||
Text("N/A")
|
||||
.foregroundColor(Color.gray)
|
||||
.font(.system(size: 15, design: .monospaced))
|
||||
.truncationMode(.tail)
|
||||
}
|
||||
*/
|
||||
})
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle("Yggdrasil")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct StatusView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var config = ConfigurationProxy()
|
||||
|
||||
StatusView(yggdrasilConfiguration: $config)
|
||||
}
|
||||
}
|
22
YggdrasilSwiftUI/YggdrasilSwiftUI.entitlements
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?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>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.vpn.api</key>
|
||||
<array>
|
||||
<string>allow-vpn</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.eu.neilalexander.yggdrasil</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|