From d2dd506a94f3f6f49dfe0909306be78d30e420ba Mon Sep 17 00:00:00 2001 From: Beingpax Date: Sun, 28 Dec 2025 15:30:48 +0545 Subject: [PATCH] Move transcription history to separate window - Add HistoryWindowController for native macOS window management - Update MenuBarManager to support window opening - Modify sidebar to open history in separate window instead of inline - Update navigation handlers to use window controller - Settings remains inline as before --- VoiceInk/HistoryWindowController.swift | 76 ++++++++++++++++++++++++++ VoiceInk/MenuBarManager.swift | 26 +++++++-- VoiceInk/Views/ContentView.swift | 58 +++++++++++++++----- VoiceInk/Views/MenuBarView.swift | 2 +- VoiceInk/VoiceInk.swift | 5 +- 5 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 VoiceInk/HistoryWindowController.swift diff --git a/VoiceInk/HistoryWindowController.swift b/VoiceInk/HistoryWindowController.swift new file mode 100644 index 0000000..845f385 --- /dev/null +++ b/VoiceInk/HistoryWindowController.swift @@ -0,0 +1,76 @@ +import SwiftUI +import SwiftData +import AppKit + +class HistoryWindowController: NSObject, NSWindowDelegate { + static let shared = HistoryWindowController() + + private var historyWindow: NSWindow? + private let windowIdentifier = NSUserInterfaceItemIdentifier("com.prakashjoshipax.voiceink.historyWindow") + private let windowAutosaveName = NSWindow.FrameAutosaveName("VoiceInkHistoryWindowFrame") + + private override init() { + super.init() + } + + func showHistoryWindow(modelContainer: ModelContainer) { + if let existingWindow = historyWindow, existingWindow.isVisible { + existingWindow.makeKeyAndOrderFront(nil) + NSApplication.shared.activate(ignoringOtherApps: true) + return + } + + let window = createHistoryWindow(modelContainer: modelContainer) + historyWindow = window + window.makeKeyAndOrderFront(nil) + NSApplication.shared.activate(ignoringOtherApps: true) + } + + private func createHistoryWindow(modelContainer: ModelContainer) -> NSWindow { + let historyView = TranscriptionHistoryView() + .modelContainer(modelContainer) + .frame(minWidth: 800, minHeight: 600) + + let hostingController = NSHostingController(rootView: historyView) + + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 900, height: 700), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, + defer: false + ) + + window.contentViewController = hostingController + window.title = "VoiceInk — Transcription History" + window.identifier = windowIdentifier + window.delegate = self + window.titlebarAppearsTransparent = true + window.titleVisibility = .visible + window.backgroundColor = NSColor.windowBackgroundColor + window.isReleasedWhenClosed = false + window.collectionBehavior = [.fullScreenPrimary] + window.minSize = NSSize(width: 700, height: 500) + + window.setFrameAutosaveName(windowAutosaveName) + if !window.setFrameUsingName(windowAutosaveName) { + window.center() + } + + return window + } + + // MARK: - NSWindowDelegate + + func windowWillClose(_ notification: Notification) { + guard let window = notification.object as? NSWindow, + window.identifier == windowIdentifier else { return } + + historyWindow = nil + } + + func windowDidBecomeKey(_ notification: Notification) { + guard let window = notification.object as? NSWindow, + window.identifier == windowIdentifier else { return } + NSApplication.shared.activate(ignoringOtherApps: true) + } +} diff --git a/VoiceInk/MenuBarManager.swift b/VoiceInk/MenuBarManager.swift index e9aede1..ce51475 100644 --- a/VoiceInk/MenuBarManager.swift +++ b/VoiceInk/MenuBarManager.swift @@ -1,4 +1,5 @@ import SwiftUI +import SwiftData import AppKit class MenuBarManager: ObservableObject { @@ -8,12 +9,17 @@ class MenuBarManager: ObservableObject { updateAppActivationPolicy() } } - - + + private var modelContainer: ModelContainer? + init() { self.isMenuBarOnly = UserDefaults.standard.bool(forKey: "IsMenuBarOnly") updateAppActivationPolicy() } + + func configure(modelContainer: ModelContainer) { + self.modelContainer = modelContainer + } func toggleMenuBarOnly() { isMenuBarOnly.toggle() @@ -54,17 +60,17 @@ class MenuBarManager: ObservableObject { func openMainWindowAndNavigate(to destination: String) { print("MenuBarManager: Navigating to \(destination)") - + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - + self.applyActivationPolicy() - + guard WindowManager.shared.showMainWindow() != nil else { print("MenuBarManager: Unable to show main window for navigation") return } - + // Post a notification to navigate to the desired destination DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { NotificationCenter.default.post( @@ -76,4 +82,12 @@ class MenuBarManager: ObservableObject { } } } + + func openHistoryWindow() { + guard let modelContainer = modelContainer else { + print("MenuBarManager: ModelContainer not configured") + return + } + HistoryWindowController.shared.showHistoryWindow(modelContainer: modelContainer) + } } diff --git a/VoiceInk/Views/ContentView.swift b/VoiceInk/Views/ContentView.swift index 34265ee..ea3a3e7 100644 --- a/VoiceInk/Views/ContentView.swift +++ b/VoiceInk/Views/ContentView.swift @@ -106,22 +106,47 @@ struct ContentView: View { ForEach(visibleViewTypes) { viewType in Section { - NavigationLink(value: viewType) { - HStack(spacing: 12) { - Image(systemName: viewType.icon) - .font(.system(size: 18, weight: .medium)) - .frame(width: 24, height: 24) + if viewType == .history { + // History opens in separate window instead of inline + Button(action: { + HistoryWindowController.shared.showHistoryWindow( + modelContainer: modelContext.container + ) + }) { + HStack(spacing: 12) { + Image(systemName: viewType.icon) + .font(.system(size: 18, weight: .medium)) + .frame(width: 24, height: 24) - Text(viewType.rawValue) - .font(.system(size: 14, weight: .medium)) + Text(viewType.rawValue) + .font(.system(size: 14, weight: .medium)) - Spacer() + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 2) } - .padding(.vertical, 8) - .padding(.horizontal, 2) + .buttonStyle(.plain) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .listRowSeparator(.hidden) + } else { + NavigationLink(value: viewType) { + HStack(spacing: 12) { + Image(systemName: viewType.icon) + .font(.system(size: 18, weight: .medium)) + .frame(width: 24, height: 24) + + Text(viewType.rawValue) + .font(.system(size: 14, weight: .medium)) + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 2) + } + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .listRowSeparator(.hidden) } - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - .listRowSeparator(.hidden) } } } @@ -150,7 +175,10 @@ struct ContentView: View { case "VoiceInk Pro": selectedView = .license case "History": - selectedView = .history + // Open History in separate window instead of inline + HistoryWindowController.shared.showHistoryWindow( + modelContainer: modelContext.container + ) case "Permissions": selectedView = .permissions case "Enhancement": @@ -178,7 +206,9 @@ struct ContentView: View { case .transcribeAudio: AudioTranscribeView() case .history: - TranscriptionHistoryView() + // History now opens in separate window, not shown inline + Text("History") + .foregroundColor(.secondary) case .audioInput: AudioInputSettingsView() case .dictionary: diff --git a/VoiceInk/Views/MenuBarView.swift b/VoiceInk/Views/MenuBarView.swift index 21b54e2..b755591 100644 --- a/VoiceInk/Views/MenuBarView.swift +++ b/VoiceInk/Views/MenuBarView.swift @@ -199,7 +199,7 @@ struct MenuBarView: View { .keyboardShortcut("c", modifiers: [.command, .shift]) Button("History") { - menuBarManager.openMainWindowAndNavigate(to: "History") + menuBarManager.openHistoryWindow() } .keyboardShortcut("h", modifiers: [.command, .shift]) diff --git a/VoiceInk/VoiceInk.swift b/VoiceInk/VoiceInk.swift index 77d8c19..71dacac 100644 --- a/VoiceInk/VoiceInk.swift +++ b/VoiceInk/VoiceInk.swift @@ -109,6 +109,9 @@ struct VoiceInkApp: App { let menuBarManager = MenuBarManager() _menuBarManager = StateObject(wrappedValue: menuBarManager) + // Configure MenuBarManager with ModelContainer for window management + menuBarManager.configure(modelContainer: container) + let activeWindowService = ActiveWindowService.shared activeWindowService.configure(with: enhancementService) activeWindowService.configureWhisperState(whisperState) @@ -277,7 +280,7 @@ struct VoiceInkApp: App { .windowStyle(.hiddenTitleBar) .commands { CommandGroup(replacing: .newItem) { } - + CommandGroup(after: .appInfo) { CheckForUpdatesView(updaterViewModel: updaterViewModel) }