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:
parent
3a2721e150
commit
d2dd506a94
76
VoiceInk/HistoryWindowController.swift
Normal file
76
VoiceInk/HistoryWindowController.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
class MenuBarManager: ObservableObject {
|
class MenuBarManager: ObservableObject {
|
||||||
@ -8,12 +9,17 @@ class MenuBarManager: ObservableObject {
|
|||||||
updateAppActivationPolicy()
|
updateAppActivationPolicy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var modelContainer: ModelContainer?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.isMenuBarOnly = UserDefaults.standard.bool(forKey: "IsMenuBarOnly")
|
self.isMenuBarOnly = UserDefaults.standard.bool(forKey: "IsMenuBarOnly")
|
||||||
updateAppActivationPolicy()
|
updateAppActivationPolicy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configure(modelContainer: ModelContainer) {
|
||||||
|
self.modelContainer = modelContainer
|
||||||
|
}
|
||||||
|
|
||||||
func toggleMenuBarOnly() {
|
func toggleMenuBarOnly() {
|
||||||
isMenuBarOnly.toggle()
|
isMenuBarOnly.toggle()
|
||||||
@ -54,17 +60,17 @@ class MenuBarManager: ObservableObject {
|
|||||||
|
|
||||||
func openMainWindowAndNavigate(to destination: String) {
|
func openMainWindowAndNavigate(to destination: String) {
|
||||||
print("MenuBarManager: Navigating to \(destination)")
|
print("MenuBarManager: Navigating to \(destination)")
|
||||||
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
self.applyActivationPolicy()
|
self.applyActivationPolicy()
|
||||||
|
|
||||||
guard WindowManager.shared.showMainWindow() != nil else {
|
guard WindowManager.shared.showMainWindow() != nil else {
|
||||||
print("MenuBarManager: Unable to show main window for navigation")
|
print("MenuBarManager: Unable to show main window for navigation")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post a notification to navigate to the desired destination
|
// Post a notification to navigate to the desired destination
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
NotificationCenter.default.post(
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,22 +106,47 @@ struct ContentView: View {
|
|||||||
|
|
||||||
ForEach(visibleViewTypes) { viewType in
|
ForEach(visibleViewTypes) { viewType in
|
||||||
Section {
|
Section {
|
||||||
NavigationLink(value: viewType) {
|
if viewType == .history {
|
||||||
HStack(spacing: 12) {
|
// History opens in separate window instead of inline
|
||||||
Image(systemName: viewType.icon)
|
Button(action: {
|
||||||
.font(.system(size: 18, weight: .medium))
|
HistoryWindowController.shared.showHistoryWindow(
|
||||||
.frame(width: 24, height: 24)
|
modelContainer: modelContext.container
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
Image(systemName: viewType.icon)
|
||||||
|
.font(.system(size: 18, weight: .medium))
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
|
||||||
Text(viewType.rawValue)
|
Text(viewType.rawValue)
|
||||||
.font(.system(size: 14, weight: .medium))
|
.font(.system(size: 14, weight: .medium))
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 2)
|
||||||
}
|
}
|
||||||
.padding(.vertical, 8)
|
.buttonStyle(.plain)
|
||||||
.padding(.horizontal, 2)
|
.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":
|
case "VoiceInk Pro":
|
||||||
selectedView = .license
|
selectedView = .license
|
||||||
case "History":
|
case "History":
|
||||||
selectedView = .history
|
// Open History in separate window instead of inline
|
||||||
|
HistoryWindowController.shared.showHistoryWindow(
|
||||||
|
modelContainer: modelContext.container
|
||||||
|
)
|
||||||
case "Permissions":
|
case "Permissions":
|
||||||
selectedView = .permissions
|
selectedView = .permissions
|
||||||
case "Enhancement":
|
case "Enhancement":
|
||||||
@ -178,7 +206,9 @@ struct ContentView: View {
|
|||||||
case .transcribeAudio:
|
case .transcribeAudio:
|
||||||
AudioTranscribeView()
|
AudioTranscribeView()
|
||||||
case .history:
|
case .history:
|
||||||
TranscriptionHistoryView()
|
// History now opens in separate window, not shown inline
|
||||||
|
Text("History")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
case .audioInput:
|
case .audioInput:
|
||||||
AudioInputSettingsView()
|
AudioInputSettingsView()
|
||||||
case .dictionary:
|
case .dictionary:
|
||||||
|
|||||||
@ -199,7 +199,7 @@ struct MenuBarView: View {
|
|||||||
.keyboardShortcut("c", modifiers: [.command, .shift])
|
.keyboardShortcut("c", modifiers: [.command, .shift])
|
||||||
|
|
||||||
Button("History") {
|
Button("History") {
|
||||||
menuBarManager.openMainWindowAndNavigate(to: "History")
|
menuBarManager.openHistoryWindow()
|
||||||
}
|
}
|
||||||
.keyboardShortcut("h", modifiers: [.command, .shift])
|
.keyboardShortcut("h", modifiers: [.command, .shift])
|
||||||
|
|
||||||
|
|||||||
@ -109,6 +109,9 @@ struct VoiceInkApp: App {
|
|||||||
let menuBarManager = MenuBarManager()
|
let menuBarManager = MenuBarManager()
|
||||||
_menuBarManager = StateObject(wrappedValue: menuBarManager)
|
_menuBarManager = StateObject(wrappedValue: menuBarManager)
|
||||||
|
|
||||||
|
// Configure MenuBarManager with ModelContainer for window management
|
||||||
|
menuBarManager.configure(modelContainer: container)
|
||||||
|
|
||||||
let activeWindowService = ActiveWindowService.shared
|
let activeWindowService = ActiveWindowService.shared
|
||||||
activeWindowService.configure(with: enhancementService)
|
activeWindowService.configure(with: enhancementService)
|
||||||
activeWindowService.configureWhisperState(whisperState)
|
activeWindowService.configureWhisperState(whisperState)
|
||||||
@ -277,7 +280,7 @@ struct VoiceInkApp: App {
|
|||||||
.windowStyle(.hiddenTitleBar)
|
.windowStyle(.hiddenTitleBar)
|
||||||
.commands {
|
.commands {
|
||||||
CommandGroup(replacing: .newItem) { }
|
CommandGroup(replacing: .newItem) { }
|
||||||
|
|
||||||
CommandGroup(after: .appInfo) {
|
CommandGroup(after: .appInfo) {
|
||||||
CheckForUpdatesView(updaterViewModel: updaterViewModel)
|
CheckForUpdatesView(updaterViewModel: updaterViewModel)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user