This commit provides comprehensive documentation and source files for 5 critical quality of life improvements: 1. Recording duration indicator with real-time timer 2. Enhanced status display with visual feedback 3. Visible cancel button during recording 4. Keyboard shortcut cheat sheet (Cmd+?) 5. Structured logging system (AppLogger) Included Files: - Complete source files for new features - Step-by-step application guide - Detailed technical documentation - Full analysis with 40+ improvement ideas See APPLYING_QOL_IMPROVEMENTS.md for instructions on how to apply these improvements to the codebase. All changes are backward compatible with no breaking changes. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
249 lines
9.6 KiB
Swift
249 lines
9.6 KiB
Swift
import SwiftUI
|
|
import KeyboardShortcuts
|
|
|
|
struct KeyboardShortcutCheatSheet: View {
|
|
@EnvironmentObject private var hotkeyManager: HotkeyManager
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
// Header
|
|
HStack {
|
|
Text("Keyboard Shortcuts")
|
|
.font(.title2)
|
|
.fontWeight(.semibold)
|
|
|
|
Spacer()
|
|
|
|
Button(action: { dismiss() }) {
|
|
Image(systemName: "xmark.circle.fill")
|
|
.font(.title3)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.help("Close")
|
|
}
|
|
.padding()
|
|
|
|
Divider()
|
|
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 24) {
|
|
// Recording Section
|
|
ShortcutSection(title: "Recording", icon: "mic.fill", iconColor: .red) {
|
|
ShortcutRow(
|
|
action: "Start/Stop Recording",
|
|
shortcut: hotkeyManager.selectedHotkey1.displayName,
|
|
description: "Quick tap to toggle hands-free mode, hold for push-to-talk"
|
|
)
|
|
|
|
if hotkeyManager.selectedHotkey2 != .none {
|
|
ShortcutRow(
|
|
action: "Alternative Recording Trigger",
|
|
shortcut: hotkeyManager.selectedHotkey2.displayName,
|
|
description: "Secondary hotkey option"
|
|
)
|
|
}
|
|
|
|
ShortcutRow(
|
|
action: "Cancel Recording",
|
|
shortcut: "ESC ESC",
|
|
description: "Double-tap Escape to cancel current recording"
|
|
)
|
|
|
|
if let customCancel = KeyboardShortcuts.getShortcut(for: .cancelRecorder) {
|
|
ShortcutRow(
|
|
action: "Cancel (Custom)",
|
|
shortcut: customCancel.description,
|
|
description: "Custom cancel shortcut"
|
|
)
|
|
}
|
|
|
|
if hotkeyManager.isMiddleClickToggleEnabled {
|
|
ShortcutRow(
|
|
action: "Middle-Click Toggle",
|
|
shortcut: "Middle Mouse",
|
|
description: "Use middle mouse button to toggle recording"
|
|
)
|
|
}
|
|
}
|
|
|
|
// Paste Section
|
|
ShortcutSection(title: "Paste Transcriptions", icon: "doc.on.clipboard", iconColor: .blue) {
|
|
if let shortcut = KeyboardShortcuts.getShortcut(for: .pasteLastTranscription) {
|
|
ShortcutRow(
|
|
action: "Paste Last Transcript (Original)",
|
|
shortcut: shortcut.description,
|
|
description: "Paste the most recent unprocessed transcription"
|
|
)
|
|
}
|
|
|
|
if let shortcut = KeyboardShortcuts.getShortcut(for: .pasteLastEnhancement) {
|
|
ShortcutRow(
|
|
action: "Paste Last Transcript (Enhanced)",
|
|
shortcut: shortcut.description,
|
|
description: "Paste enhanced transcript, fallback to original if unavailable"
|
|
)
|
|
}
|
|
|
|
if let shortcut = KeyboardShortcuts.getShortcut(for: .retryLastTranscription) {
|
|
ShortcutRow(
|
|
action: "Retry Last Transcription",
|
|
shortcut: shortcut.description,
|
|
description: "Re-transcribe the last audio with current model"
|
|
)
|
|
}
|
|
}
|
|
|
|
// History Section
|
|
ShortcutSection(title: "History Navigation", icon: "clock.arrow.circlepath", iconColor: .purple) {
|
|
ShortcutRow(
|
|
action: "Search History",
|
|
shortcut: "⌘F",
|
|
description: "Focus the search field in History view"
|
|
)
|
|
|
|
ShortcutRow(
|
|
action: "Delete Selected",
|
|
shortcut: "⌫",
|
|
description: "Delete selected transcription entries"
|
|
)
|
|
|
|
ShortcutRow(
|
|
action: "Select All",
|
|
shortcut: "⌘A",
|
|
description: "Select all transcriptions in current view"
|
|
)
|
|
}
|
|
|
|
// General Section
|
|
ShortcutSection(title: "General", icon: "command", iconColor: .gray) {
|
|
ShortcutRow(
|
|
action: "Show This Help",
|
|
shortcut: "⌘?",
|
|
description: "Display keyboard shortcuts reference"
|
|
)
|
|
|
|
ShortcutRow(
|
|
action: "Open Settings",
|
|
shortcut: "⌘,",
|
|
description: "Open application settings"
|
|
)
|
|
|
|
ShortcutRow(
|
|
action: "Close Window",
|
|
shortcut: "⌘W",
|
|
description: "Close current window"
|
|
)
|
|
|
|
ShortcutRow(
|
|
action: "Quit VoiceLink",
|
|
shortcut: "⌘Q",
|
|
description: "Exit the application"
|
|
)
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
|
|
Divider()
|
|
|
|
// Footer
|
|
HStack {
|
|
Text("Customize shortcuts in Settings")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
|
|
Spacer()
|
|
|
|
Button("Open Settings") {
|
|
dismiss()
|
|
NotificationCenter.default.post(name: .navigateToDestination, object: nil, userInfo: ["destination": "Settings"])
|
|
}
|
|
.controlSize(.small)
|
|
}
|
|
.padding()
|
|
}
|
|
.frame(width: 600, height: 700)
|
|
.background(Color(NSColor.windowBackgroundColor))
|
|
}
|
|
}
|
|
|
|
struct ShortcutSection<Content: View>: View {
|
|
let title: String
|
|
let icon: String
|
|
let iconColor: Color
|
|
let content: Content
|
|
|
|
init(title: String, icon: String, iconColor: Color, @ViewBuilder content: () -> Content) {
|
|
self.title = title
|
|
self.icon = icon
|
|
self.iconColor = iconColor
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: icon)
|
|
.foregroundColor(iconColor)
|
|
.font(.system(size: 16, weight: .semibold))
|
|
|
|
Text(title)
|
|
.font(.headline)
|
|
.fontWeight(.semibold)
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
content
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ShortcutRow: View {
|
|
let action: String
|
|
let shortcut: String
|
|
let description: String?
|
|
|
|
init(action: String, shortcut: String, description: String? = nil) {
|
|
self.action = action
|
|
self.shortcut = shortcut
|
|
self.description = description
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 12) {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(action)
|
|
.font(.system(size: 13, weight: .medium))
|
|
|
|
if let description = description {
|
|
Text(description)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text(shortcut)
|
|
.font(.system(size: 12, weight: .medium, design: .monospaced))
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 4)
|
|
.background(Color(NSColor.controlBackgroundColor))
|
|
.cornerRadius(4)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 4)
|
|
.stroke(Color.secondary.opacity(0.3), lineWidth: 1)
|
|
)
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
KeyboardShortcutCheatSheet()
|
|
.environmentObject(HotkeyManager(whisperState: WhisperState(modelContext: ModelContext(try! ModelContainer(for: Transcription.self)))))
|
|
}
|