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
}
}
}
}