|
|
@@ -8,8 +8,23 @@
|
|
|
import Cocoa
|
|
|
|
|
|
@main
|
|
|
-class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
+class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
private let darkModeDefaultsKey = "settings.darkModeEnabled"
|
|
|
+ private let loginStateKey = "zoom_app.isLoggedIn"
|
|
|
+ private var statusItem: NSStatusItem?
|
|
|
+ private var statusMenu: NSMenu?
|
|
|
+
|
|
|
+ private enum MenuItemTag: Int {
|
|
|
+ case openHome = 10
|
|
|
+ case openMeetings = 11
|
|
|
+ case openScheduler = 12
|
|
|
+ case openSettings = 13
|
|
|
+ case newMeeting = 20
|
|
|
+ case joinMeeting = 21
|
|
|
+ case scheduleMeeting = 22
|
|
|
+ case auth = 30
|
|
|
+ case quit = 99
|
|
|
+ }
|
|
|
|
|
|
private enum DefaultWindowSize {
|
|
|
static let width: CGFloat = 1020
|
|
|
@@ -26,6 +41,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
UserDefaults.standard.set(darkEnabled, forKey: darkModeDefaultsKey)
|
|
|
}
|
|
|
NSApp.appearance = NSAppearance(named: darkEnabled ? .darkAqua : .aqua)
|
|
|
+ configureStatusItem()
|
|
|
|
|
|
// Force a consistent launch size (avoid state restoration overriding it).
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
|
@@ -62,5 +78,126 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
return style?.lowercased() == "dark"
|
|
|
}
|
|
|
|
|
|
+ private func configureStatusItem() {
|
|
|
+ let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
|
+ if let button = item.button {
|
|
|
+ let symbolConfig = NSImage.SymbolConfiguration(pointSize: 14, weight: .semibold)
|
|
|
+ if let base = NSImage(systemSymbolName: "video.fill", accessibilityDescription: "Zoom App"),
|
|
|
+ let image = base.withSymbolConfiguration(symbolConfig) {
|
|
|
+ image.isTemplate = true
|
|
|
+ button.image = image
|
|
|
+ button.imagePosition = .imageOnly
|
|
|
+ button.toolTip = "Zoom App"
|
|
|
+ } else {
|
|
|
+ button.title = "Z"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let menu = NSMenu()
|
|
|
+ menu.delegate = self
|
|
|
+ item.menu = menu
|
|
|
+ statusItem = item
|
|
|
+ statusMenu = menu
|
|
|
+ rebuildStatusMenu()
|
|
|
+ }
|
|
|
+
|
|
|
+ func menuWillOpen(_ menu: NSMenu) {
|
|
|
+ rebuildStatusMenu()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func rebuildStatusMenu() {
|
|
|
+ guard let menu = statusMenu else { return }
|
|
|
+ menu.removeAllItems()
|
|
|
+
|
|
|
+ appendMenuItem("Open Home", tag: .openHome, to: menu)
|
|
|
+ appendMenuItem("Open Meetings", tag: .openMeetings, to: menu)
|
|
|
+ appendMenuItem("Open Scheduler", tag: .openScheduler, to: menu)
|
|
|
+ appendMenuItem("Open Settings", tag: .openSettings, to: menu)
|
|
|
+ menu.addItem(.separator())
|
|
|
+ appendMenuItem("New Meeting", tag: .newMeeting, to: menu)
|
|
|
+ appendMenuItem("Join Meeting", tag: .joinMeeting, to: menu)
|
|
|
+ appendMenuItem("Schedule Meeting", tag: .scheduleMeeting, to: menu)
|
|
|
+ menu.addItem(.separator())
|
|
|
+
|
|
|
+ let authTitle = isUserLoggedInForMenu() ? "Sign Out" : "Sign In"
|
|
|
+ appendMenuItem(authTitle, tag: .auth, to: menu)
|
|
|
+ menu.addItem(.separator())
|
|
|
+ appendMenuItem("Quit", tag: .quit, to: menu)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func appendMenuItem(_ title: String, tag: MenuItemTag, to menu: NSMenu) {
|
|
|
+ let item = NSMenuItem(title: title, action: #selector(handleStatusMenuAction(_:)), keyEquivalent: "")
|
|
|
+ item.target = self
|
|
|
+ item.tag = tag.rawValue
|
|
|
+ menu.addItem(item)
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc
|
|
|
+ private func handleStatusMenuAction(_ sender: NSMenuItem) {
|
|
|
+ guard let action = MenuItemTag(rawValue: sender.tag) else { return }
|
|
|
+
|
|
|
+ if action == .quit {
|
|
|
+ NSApp.terminate(nil)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ focusMainAppWindow()
|
|
|
+ guard let controller = mainViewController() else { return }
|
|
|
+
|
|
|
+ switch action {
|
|
|
+ case .openHome:
|
|
|
+ controller.menuBarShowSection(.home)
|
|
|
+ case .openMeetings:
|
|
|
+ controller.menuBarShowSection(.meetings)
|
|
|
+ case .openScheduler:
|
|
|
+ controller.menuBarShowSection(.scheduler)
|
|
|
+ case .openSettings:
|
|
|
+ controller.menuBarShowSection(.settings)
|
|
|
+ case .newMeeting:
|
|
|
+ controller.menuBarTriggerNewMeeting()
|
|
|
+ case .joinMeeting:
|
|
|
+ controller.menuBarTriggerJoinMeeting()
|
|
|
+ case .scheduleMeeting:
|
|
|
+ controller.menuBarTriggerScheduleMeeting()
|
|
|
+ case .auth:
|
|
|
+ controller.menuBarTriggerAuthAction()
|
|
|
+ case .quit:
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func focusMainAppWindow() {
|
|
|
+ NSApp.activate(ignoringOtherApps: true)
|
|
|
+ if let window = windowHostingViewController() {
|
|
|
+ window.makeKeyAndOrderFront(nil)
|
|
|
+ } else {
|
|
|
+ NSApp.windows.first?.makeKeyAndOrderFront(nil)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func windowHostingViewController() -> NSWindow? {
|
|
|
+ NSApp.windows.first(where: { $0.contentViewController is ViewController })
|
|
|
+ }
|
|
|
+
|
|
|
+ private func mainViewController() -> ViewController? {
|
|
|
+ if let controller = NSApp.keyWindow?.contentViewController as? ViewController {
|
|
|
+ return controller
|
|
|
+ }
|
|
|
+ if let controller = NSApp.mainWindow?.contentViewController as? ViewController {
|
|
|
+ return controller
|
|
|
+ }
|
|
|
+ if let controller = windowHostingViewController()?.contentViewController as? ViewController {
|
|
|
+ return controller
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ private func isUserLoggedInForMenu() -> Bool {
|
|
|
+ if let controller = mainViewController() {
|
|
|
+ return controller.menuBarIsUserLoggedIn()
|
|
|
+ }
|
|
|
+ return UserDefaults.standard.bool(forKey: loginStateKey)
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|