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:
Conrad Kramer 2023-06-23 18:22:26 -04:00
parent 5438542284
commit 32e4e9d1d7
4 changed files with 108 additions and 93 deletions

View 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

View file

@ -1,12 +1,17 @@
import NetworkExtension
import SwiftUI
@main
@MainActor
struct BurrowApp: App {
//To connect to the App Delegate
@NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
static let tunnel = Tunnel { manager, proto in
proto.serverAddress = "hackclub.com"
manager.localizedDescription = "Burrow"
}
#if os(macOS)
@NSApplicationDelegateAdaptor(AppDelegate.self)
var delegate
#endif
var body: some Scene {
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)
}
}
}

View file

@ -7,63 +7,55 @@
import SwiftUI
struct MenuView: View {
@State private var isToggled = false
struct MenuItemToggleView: View {
@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 {
VStack {
HStack {
Text("Burrow")
.fontWeight(.bold)
Spacer()
Toggle("", isOn: $isToggled)
.toggleStyle(SwitchToggleStyle(tint: .blue))
.onChange(of: isToggled) { value in
if value {
start()
} else {
stop()
}
print("Toggle value: \(value)")
}
}
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)
}
HStack {
Text("Burrow")
.font(.headline)
Spacer()
Toggle("Burrow", isOn: tunnel.isOn)
.labelsHidden()
.disabled(tunnel.isDisabled)
.toggleStyle(.switch)
}
.frame(width: 250)
.padding(16)
.padding(.horizontal, 4)
.padding(10)
.frame(minWidth: 300, minHeight: 32, maxHeight: 32)
.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
}
}
}
}

View file

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
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 */; };
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 */; };
@ -46,6 +47,7 @@
/* Begin PBXFileReference section */
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>"; };
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>"; };
@ -149,6 +151,7 @@
children = (
43AA26D62A0FFFD000F14CE6 /* Menu */,
D05B9F7529E39EEC008CB1F9 /* BurrowApp.swift */,
D00AA8962A4669BC005C8102 /* AppDelegate.swift */,
D05B9F7729E39EEC008CB1F9 /* TunnelView.swift */,
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */,
D0BCC5FE2A086E1C00AD070D /* Status.swift */,
@ -313,6 +316,7 @@
43AA26D82A10004900F14CE6 /* MenuView.swift in Sources */,
D05B9F7829E39EEC008CB1F9 /* TunnelView.swift in Sources */,
D0BCC5FF2A086E1C00AD070D /* Status.swift in Sources */,
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */,
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */,
D0BCC5FD2A086D4700AD070D /* NetworkExtension+Async.swift in Sources */,
);