90 lines
3.2 KiB
Swift
90 lines
3.2 KiB
Swift
//
|
|
// leanring_buddyApp.swift
|
|
// leanring-buddy
|
|
//
|
|
// Menu bar-only companion app. No dock icon, no main window — just an
|
|
// always-available status item in the macOS menu bar. Clicking the icon
|
|
// opens a floating panel with companion voice controls.
|
|
//
|
|
|
|
import ServiceManagement
|
|
import SwiftUI
|
|
import Sparkle
|
|
|
|
@main
|
|
struct leanring_buddyApp: App {
|
|
@NSApplicationDelegateAdaptor(CompanionAppDelegate.self) var appDelegate
|
|
|
|
var body: some Scene {
|
|
// The app lives entirely in the menu bar panel managed by the AppDelegate.
|
|
// This empty Settings scene satisfies SwiftUI's requirement for at least
|
|
// one scene but is never shown (LSUIElement=true removes the app menu).
|
|
Settings {
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Manages the companion lifecycle: creates the menu bar panel and starts
|
|
/// the companion voice pipeline on launch.
|
|
@MainActor
|
|
final class CompanionAppDelegate: NSObject, NSApplicationDelegate {
|
|
private var menuBarPanelManager: MenuBarPanelManager?
|
|
private let companionManager = CompanionManager()
|
|
private var sparkleUpdaterController: SPUStandardUpdaterController?
|
|
|
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
print("🎯 Clicky: Starting...")
|
|
print("🎯 Clicky: Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown")")
|
|
|
|
UserDefaults.standard.register(defaults: ["NSInitialToolTipDelay": 0])
|
|
|
|
ClickyAnalytics.configure()
|
|
ClickyAnalytics.trackAppOpened()
|
|
|
|
menuBarPanelManager = MenuBarPanelManager(companionManager: companionManager)
|
|
companionManager.start()
|
|
// Auto-open the panel if the user still needs to do something:
|
|
// either they haven't onboarded yet, or permissions were revoked.
|
|
if !companionManager.hasCompletedOnboarding || !companionManager.allPermissionsGranted {
|
|
menuBarPanelManager?.showPanelOnLaunch()
|
|
}
|
|
registerAsLoginItemIfNeeded()
|
|
// startSparkleUpdater()
|
|
}
|
|
|
|
func applicationWillTerminate(_ notification: Notification) {
|
|
companionManager.stop()
|
|
}
|
|
|
|
/// Registers the app as a login item so it launches automatically on
|
|
/// startup. Uses SMAppService which shows the app in System Settings >
|
|
/// General > Login Items, letting the user toggle it off if they want.
|
|
private func registerAsLoginItemIfNeeded() {
|
|
let loginItemService = SMAppService.mainApp
|
|
if loginItemService.status != .enabled {
|
|
do {
|
|
try loginItemService.register()
|
|
print("🎯 Clicky: Registered as login item")
|
|
} catch {
|
|
print("⚠️ Clicky: Failed to register as login item: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startSparkleUpdater() {
|
|
let updaterController = SPUStandardUpdaterController(
|
|
startingUpdater: false,
|
|
updaterDelegate: nil,
|
|
userDriverDelegate: nil
|
|
)
|
|
self.sparkleUpdaterController = updaterController
|
|
|
|
do {
|
|
try updaterController.updater.start()
|
|
} catch {
|
|
print("⚠️ Clicky: Sparkle updater failed to start: \(error)")
|
|
}
|
|
}
|
|
}
|