Files
meetingnotes/meetingnotes/MeetingnotesApp.swift
T

191 lines
5.8 KiB
Swift

//
// meetingnotesApp.swift
// meetingnotes
//
// Created by Owen Gretzinger on 2025-07-10.
//
import SwiftUI
import AppKit
import Sparkle
import PostHog
@main
struct MeetingnotesApp: App {
private let updaterController: SPUStandardUpdaterController
@StateObject private var recordingSessionManager = RecordingSessionManager.shared
init() {
updaterController = SPUStandardUpdaterController(updaterDelegate: nil, userDriverDelegate: nil)
// Setup PostHog analytics for anonymous tracking
let posthogAPIKey = "phc_Wt8sWUzUF7YPF50aQ0B1qbfA5SJWWR341zmXCaIaIRJ"
let posthogHost = "https://us.i.posthog.com"
let config = PostHogConfig(apiKey: posthogAPIKey, host: posthogHost)
// Only capture anonymous events
config.personProfiles = .never
// Enable lifecycle and screen view autocapture
config.captureApplicationLifecycleEvents = true
config.captureScreenViews = true
PostHogSDK.shared.setup(config)
// Register environment as a super property
#if DEBUG
PostHogSDK.shared.register(["environment": "dev"] )
#else
PostHogSDK.shared.register(["environment": "prod"] )
#endif
}
var body: some Scene {
WindowGroup("Meetingnotes", id: "main") {
ContentView()
.frame(minWidth: 700, minHeight: 400)
.background(MainWindowConfigurator())
}
.windowResizability(.automatic)
.defaultSize(width: 1000, height: 600)
.commands {
CommandGroup(after: .appInfo) {
CheckForUpdatesView(updater: updaterController.updater)
Divider()
Button("Show Audio Folder") {
LocalStorageManager.shared.showAudioFolderInFinder()
}
}
}
MenuBarExtra {
MeetingnotesMenu(
recordingSessionManager: recordingSessionManager,
updater: updaterController.updater
)
} label: {
Image(systemName: recordingSessionManager.isRecording ? "record.circle.fill" : "waveform")
.accessibilityLabel(recordingSessionManager.isRecording ? "Meetingnotes recording" : "Meetingnotes")
}
}
}
private struct MeetingnotesMenu: View {
@ObservedObject var recordingSessionManager: RecordingSessionManager
let updater: SPUUpdater
@Environment(\.openWindow) private var openWindow
var body: some View {
if recordingSessionManager.isRecording {
Label("Recording", systemImage: "record.circle.fill")
} else if recordingSessionManager.isProcessing {
Label("Processing audio", systemImage: "hourglass")
}
Button {
MainWindowController.shared.show(using: openWindow)
} label: {
Label("Open Meetingnotes", systemImage: "macwindow")
}
Button {
LocalStorageManager.shared.showAudioFolderInFinder()
} label: {
Label("Show Audio Folder", systemImage: "folder")
}
Divider()
Button {
updater.checkForUpdates()
} label: {
Label("Check for Updates...", systemImage: "arrow.triangle.2.circlepath")
}
.keyboardShortcut("u")
Button {
NSApplication.shared.terminate(nil)
} label: {
Label("Quit Meetingnotes", systemImage: "power")
}
.keyboardShortcut("q")
}
}
@MainActor
private final class MainWindowController: NSObject {
static let shared = MainWindowController()
private weak var window: NSWindow?
func configure(_ window: NSWindow) {
if self.window !== window {
if let previousWindow = self.window {
NotificationCenter.default.removeObserver(
self,
name: NSWindow.willMiniaturizeNotification,
object: previousWindow
)
}
self.window = window
NotificationCenter.default.addObserver(
self,
selector: #selector(windowWillMiniaturize(_:)),
name: NSWindow.willMiniaturizeNotification,
object: window
)
}
if let minimizeButton = window.standardWindowButton(.miniaturizeButton) {
minimizeButton.target = self
minimizeButton.action = #selector(hideWindow(_:))
}
}
func show(using openWindow: OpenWindowAction) {
if let window {
window.makeKeyAndOrderFront(nil)
} else {
openWindow(id: "main")
}
NSApplication.shared.activate(ignoringOtherApps: true)
}
@objc private func hideWindow(_ sender: NSButton) {
sender.window?.orderOut(nil)
}
@objc private func windowWillMiniaturize(_ notification: Notification) {
guard let window = notification.object as? NSWindow else { return }
DispatchQueue.main.async {
window.deminiaturize(nil)
window.orderOut(nil)
}
}
}
private struct MainWindowConfigurator: NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
let view = NSView()
configureWindow(for: view)
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
configureWindow(for: nsView)
}
private func configureWindow(for view: NSView) {
DispatchQueue.main.async {
guard let window = view.window else { return }
MainWindowController.shared.configure(window)
}
}
}
struct CheckForUpdatesView: View {
let updater: SPUUpdater
var body: some View {
Button("Check for Updates...") {
updater.checkForUpdates()
}
.keyboardShortcut("u", modifiers: .command)
}
}