Selaa lähdekoodia

Add a menu bar app menu with focused action routing.

This introduces a dynamic status-item menu that brings the main window forward and reuses existing ViewController flows for navigation, meeting actions, and sign in/sign out so the menu stays consistent with in-app behavior.

Made-with: Cursor
huzaifahayat12 3 kuukautta sitten
vanhempi
sitoutus
7fc9c99340
2 muutettua tiedostoa jossa 182 lisäystä ja 1 poistoa
  1. 138 1
      zoom_app/AppDelegate.swift
  2. 44 0
      zoom_app/ViewController.swift

+ 138 - 1
zoom_app/AppDelegate.swift

@@ -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)
+    }
+
 }
 

+ 44 - 0
zoom_app/ViewController.swift

@@ -12,6 +12,13 @@ import StoreKit
 import WebKit
 
 class ViewController: NSViewController {
+    enum HomeSection: String {
+        case home = "Home"
+        case meetings = "Meetings"
+        case scheduler = "Scheduler"
+        case settings = "Settings"
+    }
+
     private let googleOAuth = GoogleOAuthService.shared
     private let zoomOAuth = ZoomOAuthService.shared
     private let loginStateKey = "zoom_app.isLoggedIn"
@@ -4946,6 +4953,43 @@ class ViewController: NSViewController {
         updateSelectedHomeSectionUI()
     }
 
+    func menuBarIsUserLoggedIn() -> Bool {
+        isUserLoggedIn()
+    }
+
+    func menuBarShowSection(_ section: HomeSection) {
+        selectedHomeSidebarItem = section.rawValue
+        updateHomeSidebarHighlight()
+        updateSelectedHomeSectionUI()
+    }
+
+    func menuBarTriggerNewMeeting() {
+        guard isUserLoggedIn() else {
+            showSimpleAlert(title: "Sign in required", message: "Please sign in with Zoom to start a new meeting.")
+            return
+        }
+        guard let url = URL(string: "https://zoom.us/start/videomeeting"), NSWorkspace.shared.open(url) else {
+            showSimpleAlert(title: "Unable to open", message: "Could not start a new Zoom meeting right now.")
+            return
+        }
+    }
+
+    func menuBarTriggerJoinMeeting() {
+        joinMeetingTapped()
+    }
+
+    func menuBarTriggerScheduleMeeting() {
+        scheduleMeetingTapped()
+    }
+
+    func menuBarTriggerAuthAction() {
+        if isUserLoggedIn() {
+            logoutTapped()
+        } else {
+            zoomPrimarySignInTapped()
+        }
+    }
+
     @MainActor
     private func updateHomeSidebarHighlight() {
         for (item, row) in homeSidebarRowViews {