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
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue