From 0993583feab433a4f4e5f30c8c51ecaee2664655 Mon Sep 17 00:00:00 2001 From: Jamil Date: Mon, 24 Jun 2024 09:54:04 -0700 Subject: [PATCH] feat(apple): Add button to show menuBar from FirstTimeView (#5505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #5500 Unfortunately showing the menubar menu _only_ on re-launch is non-trivial due to the way "re-launches" are processed in macOS. We can handle them with the `applicationDidBecomeActive` override in `AppDelegate`, but then this will be triggered whenever we sign in, open Settings, or open About window because we activate the app then as well in order to bring the Window to the foreground. There's no good to way to determine who asked us to activate either. Instead, we show the Welcome window (FirstTimeView on macOS), and in there is a new button to show the app menu to use as a fallback for users who need an alternative way to open the menu with a busy menubar. Screenshot 2024-06-23 at 5 14 57 PM --- .../Firezone/Application/FirezoneApp.swift | 7 ++++-- .../FirezoneKit/Views/FirstTimeView.swift | 22 ++++++++++++++----- .../Sources/FirezoneKit/Views/MenuBar.swift | 6 ++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/swift/apple/Firezone/Application/FirezoneApp.swift b/swift/apple/Firezone/Application/FirezoneApp.swift index ac6d32b87..a24f7d63a 100644 --- a/swift/apple/Firezone/Application/FirezoneApp.swift +++ b/swift/apple/Firezone/Application/FirezoneApp.swift @@ -36,7 +36,10 @@ struct FirezoneApp: App { "Welcome to Firezone", id: AppViewModel.WindowDefinition.main.identifier ) { - AppView(model: appViewModel) + if let menuBar = appDelegate.menuBar { + // menuBar will be initialized by this point + AppView(model: appViewModel).environmentObject(menuBar) + } } .handlesExternalEvents( matching: [AppViewModel.WindowDefinition.main.externalEventMatchString] @@ -58,7 +61,7 @@ struct FirezoneApp: App { #if os(macOS) @MainActor final class AppDelegate: NSObject, NSApplicationDelegate { - private var menuBar: MenuBar? + var menuBar: MenuBar? public var store: Store? func applicationDidFinishLaunching(_: Notification) { diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/FirstTimeView.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/FirstTimeView.swift index dd6e4103b..d23bd9f17 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/FirstTimeView.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/FirstTimeView.swift @@ -9,6 +9,8 @@ import SwiftUI #if os(macOS) struct FirstTimeView: View { + @EnvironmentObject var menuBar: MenuBar + var body: some View { VStack( alignment: .center, @@ -21,17 +23,27 @@ struct FirstTimeView: View { .padding(.horizontal, 10) Spacer() Text( - "You can sign in to Firezone by clicking on the Firezone icon in the macOS menu bar.\nYou may now close this window." + "You can sign in by clicking the Firezone icon in the macOS menu bar or clicking 'Open menu' below." ) .font(.body) .multilineTextAlignment(.center) Spacer() - Button("Close this Window") { - AppViewModel.WindowDefinition.main.window()?.close() + HStack { + Button("Close this window") { + AppViewModel.WindowDefinition.main.window()?.close() + } + .buttonStyle(.borderedProminent) + .controlSize(.large) + Button("Open menu") { + DispatchQueue.main.async { + menuBar.showMenu() + } + AppViewModel.WindowDefinition.main.window()?.close() + } + .buttonStyle(.borderedProminent) + .controlSize(.large) } - .buttonStyle(.borderedProminent) - .controlSize(.large) Spacer() .frame(maxHeight: 20) Text( diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift index 0f53cbed1..347fac208 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift @@ -16,7 +16,7 @@ import SwiftUI @MainActor // TODO: Refactor to MenuBarExtra for macOS 13+ // https://developer.apple.com/documentation/swiftui/menubarextra -public final class MenuBar: NSObject { +public final class MenuBar: NSObject, ObservableObject { private var statusItem: NSStatusItem private var resources: [Resource]? private var cancellables: Set = [] @@ -48,6 +48,10 @@ public final class MenuBar: NSObject { setupObservers() } + func showMenu() { + statusItem.button?.performClick(nil) + } + private func setupObservers() { model.store.$status .receive(on: DispatchQueue.main)