From 32e4e9d1d708a5bbec08f57384f611e31af0ad2b Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Fri, 23 Jun 2023 18:22:26 -0400 Subject: [PATCH] Refactored MenuView into MenuItemToggleView This change fixes some subtle state bugs in the switch handling by making isOn a direct function of the Tunnel. --- Apple/App/AppDelegate.swift | 51 ++++++++++++++ Apple/App/BurrowApp.swift | 48 +++---------- Apple/App/Menu/MenuView.swift | 98 ++++++++++++-------------- Apple/Burrow.xcodeproj/project.pbxproj | 4 ++ 4 files changed, 108 insertions(+), 93 deletions(-) create mode 100644 Apple/App/AppDelegate.swift diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift new file mode 100644 index 0000000..f42b52f --- /dev/null +++ b/Apple/App/AppDelegate.swift @@ -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 diff --git a/Apple/App/BurrowApp.swift b/Apple/App/BurrowApp.swift index 59907f8..6d798fb 100644 --- a/Apple/App/BurrowApp.swift +++ b/Apple/App/BurrowApp.swift @@ -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) - } - } -} - diff --git a/Apple/App/Menu/MenuView.swift b/Apple/App/Menu/MenuView.swift index 62abd66..9d8fb31 100644 --- a/Apple/App/Menu/MenuView.swift +++ b/Apple/App/Menu/MenuView.swift @@ -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 { + 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 + } + } + } +} diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index b5be4d1..f9c7454 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -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 = ""; }; + D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = ""; }; D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = ""; }; D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -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 */, );