Refactored MenuView into MenuItemToggleView
This change fixes some subtle state bugs in the switch handling by making isOn a direct function of the Tunnel.
This commit is contained in:
parent
5438542284
commit
32e4e9d1d7
4 changed files with 108 additions and 93 deletions
51
Apple/App/AppDelegate.swift
Normal file
51
Apple/App/AppDelegate.swift
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#if os(macOS)
|
||||||
|
import AppKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
private let quitItem: NSMenuItem = {
|
||||||
|
let quitItem = NSMenuItem(
|
||||||
|
title: "Quit Burrow",
|
||||||
|
action: #selector(NSApplication.terminate(_:)),
|
||||||
|
keyEquivalent: "q"
|
||||||
|
)
|
||||||
|
quitItem.target = NSApplication.shared
|
||||||
|
quitItem.keyEquivalentModifierMask = .command
|
||||||
|
return quitItem
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let toggleItem: NSMenuItem = {
|
||||||
|
let toggleView = NSHostingView(rootView: MenuItemToggleView(tunnel: BurrowApp.tunnel))
|
||||||
|
toggleView.frame.size = CGSize(width: 300, height: 32)
|
||||||
|
toggleView.autoresizingMask = [.width]
|
||||||
|
|
||||||
|
let toggleItem = NSMenuItem()
|
||||||
|
toggleItem.view = toggleView
|
||||||
|
return toggleItem
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var menu: NSMenu = {
|
||||||
|
let menu = NSMenu()
|
||||||
|
menu.items = [
|
||||||
|
toggleItem,
|
||||||
|
.separator(),
|
||||||
|
quitItem
|
||||||
|
]
|
||||||
|
return menu
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var statusItem: NSStatusItem = {
|
||||||
|
let statusBar = NSStatusBar.system
|
||||||
|
let statusItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
|
||||||
|
if let button = statusItem.button {
|
||||||
|
button.image = NSImage(systemSymbolName: "network.badge.shield.half.filled", accessibilityDescription: nil)
|
||||||
|
}
|
||||||
|
return statusItem
|
||||||
|
}()
|
||||||
|
|
||||||
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
statusItem.menu = menu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
import NetworkExtension
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@MainActor
|
@MainActor
|
||||||
struct BurrowApp: App {
|
struct BurrowApp: App {
|
||||||
//To connect to the App Delegate
|
static let tunnel = Tunnel { manager, proto in
|
||||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
|
proto.serverAddress = "hackclub.com"
|
||||||
|
manager.localizedDescription = "Burrow"
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
@NSApplicationDelegateAdaptor(AppDelegate.self)
|
||||||
|
var delegate
|
||||||
|
#endif
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
|
|
@ -14,40 +19,3 @@ struct BurrowApp: App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
||||||
|
|
||||||
static let tunnel = Tunnel { manager, proto in
|
|
||||||
proto.serverAddress = "hackclub.com"
|
|
||||||
manager.localizedDescription = "Burrow"
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusItem: NSStatusItem?
|
|
||||||
var popOver = NSPopover()
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
||||||
let menuView = MenuView(tunnel: AppDelegate.tunnel)
|
|
||||||
// Creating apopOver
|
|
||||||
popOver.behavior = .transient
|
|
||||||
popOver.animates = true
|
|
||||||
popOver.contentViewController = NSViewController()
|
|
||||||
popOver.contentViewController?.view = NSHostingView(rootView: menuView)
|
|
||||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
||||||
// Safe Check if status Button is Available or not...
|
|
||||||
if let menuButton = statusItem?.button {
|
|
||||||
let icon = "network.badge.shield.half.filled"
|
|
||||||
menuButton.image = NSImage(systemSymbolName: icon, accessibilityDescription: nil)
|
|
||||||
menuButton.action = #selector(menuButtonToggle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@objc func menuButtonToggle() {
|
|
||||||
|
|
||||||
|
|
||||||
if let menuButton = statusItem?.button {
|
|
||||||
|
|
||||||
self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.minY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,63 +7,55 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MenuView: View {
|
struct MenuItemToggleView: View {
|
||||||
@State private var isToggled = false
|
|
||||||
@ObservedObject var tunnel: Tunnel
|
@ObservedObject var tunnel: Tunnel
|
||||||
|
|
||||||
private func start() {
|
|
||||||
|
|
||||||
do {
|
|
||||||
try tunnel.start()
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func stop() {
|
|
||||||
tunnel.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configure() {
|
|
||||||
Task { try await tunnel.configure() }
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Burrow")
|
Text("Burrow")
|
||||||
.fontWeight(.bold)
|
.font(.headline)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
Toggle("", isOn: $isToggled)
|
Toggle("Burrow", isOn: tunnel.isOn)
|
||||||
.toggleStyle(SwitchToggleStyle(tint: .blue))
|
.labelsHidden()
|
||||||
.onChange(of: isToggled) { value in
|
.disabled(tunnel.isDisabled)
|
||||||
if value {
|
.toggleStyle(.switch)
|
||||||
start()
|
|
||||||
} else {
|
|
||||||
stop()
|
|
||||||
}
|
}
|
||||||
print("Toggle value: \(value)")
|
.padding(.horizontal, 4)
|
||||||
}
|
.padding(10)
|
||||||
}
|
.frame(minWidth: 300, minHeight: 32, maxHeight: 32)
|
||||||
Divider()
|
|
||||||
switch tunnel.status {
|
|
||||||
case .permissionRequired:
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("Burrow requires additional permissions to function optimally on your machine. Please grant the necessary permissions to ensure smooth operation.")
|
|
||||||
.font(.caption)
|
|
||||||
.truncationMode(.tail)
|
|
||||||
|
|
||||||
Button("Grant Permissions", action: configure)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
|
|
||||||
Text("Burrow is equipped with the necessary permissions to operate seamlessly on your device.")
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: 250)
|
|
||||||
.padding(16)
|
|
||||||
.task { await tunnel.update() }
|
.task { await tunnel.update() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Tunnel {
|
||||||
|
var isDisabled: Bool {
|
||||||
|
switch self.status {
|
||||||
|
case .disconnected, .permissionRequired, .connected:
|
||||||
|
return false
|
||||||
|
case .unknown, .disabled, .connecting, .reasserting, .disconnecting, .invalid, .configurationReadWriteFailed:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isOn: Binding<Bool> {
|
||||||
|
Binding {
|
||||||
|
switch self.status {
|
||||||
|
case .unknown, .disabled, .disconnecting, .disconnected, .invalid, .permissionRequired, .configurationReadWriteFailed:
|
||||||
|
return false
|
||||||
|
case .connecting, .reasserting, .connected:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} set: { newValue in
|
||||||
|
switch (self.status, newValue) {
|
||||||
|
case (.permissionRequired, true):
|
||||||
|
Task { try await self.configure() }
|
||||||
|
case (.disconnected, true):
|
||||||
|
try? self.start()
|
||||||
|
case (.connected, false):
|
||||||
|
self.stop()
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; };
|
43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuView.swift */; };
|
||||||
|
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; };
|
||||||
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
|
D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; };
|
||||||
D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; };
|
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */; };
|
||||||
|
|
@ -46,6 +47,7 @@
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
43AA26D72A10004900F14CE6 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; };
|
43AA26D72A10004900F14CE6 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; };
|
||||||
|
D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = "<group>"; };
|
D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = "<group>"; };
|
||||||
D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = "<group>"; };
|
D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = "<group>"; };
|
||||||
D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
|
@ -149,6 +151,7 @@
|
||||||
children = (
|
children = (
|
||||||
43AA26D62A0FFFD000F14CE6 /* Menu */,
|
43AA26D62A0FFFD000F14CE6 /* Menu */,
|
||||||
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */,
|
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */,
|
||||||
|
D00AA8962A4669BC005C8102 /* AppDelegate.swift */,
|
||||||
D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */,
|
D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */,
|
||||||
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */,
|
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */,
|
||||||
D0BCC5FE2A086E1C00AD070D /* Status.swift */,
|
D0BCC5FE2A086E1C00AD070D /* Status.swift */,
|
||||||
|
|
@ -313,6 +316,7 @@
|
||||||
43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */,
|
43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */,
|
||||||
D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */,
|
D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */,
|
||||||
D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */,
|
D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */,
|
||||||
|
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */,
|
||||||
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */,
|
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */,
|
||||||
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */,
|
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue