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
This commit is contained in:
Beingpax 2025-12-28 15:30:48 +05:45
parent 3a2721e150
commit d2dd506a94
5 changed files with 145 additions and 22 deletions

View File

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

View File

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

View File

@ -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:

View File

@ -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])

View File

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