Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ Open Settings from the menu bar popover or with **⌘,**
- **Hotkey** - Choose from common presets or record a custom activation key directly from your keyboard. The selected key is consumed by VocaMac while the app is running.
- **Language** - Auto-detect or specify (English, Spanish, French, German, Chinese, Japanese, and more)
- **Launch at login**
- **Menu bar icon** - Show or hide the VocaMac icon in the system menu bar

### Audio
- **Max recording duration** - 30s, 60s, 120s, or 300s
Expand Down
8 changes: 7 additions & 1 deletion Sources/VocaMac/App/VocaMacApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,11 @@ struct VocaMacApp: App {
@StateObject private var settingsManager = SettingsWindowManager()
@StateObject private var updateWindowManager = UpdateWindowManager()
@StateObject private var onboardingManager = OnboardingWindowManager()
@AppStorage("vocamac.showTrayIcon") private var showTrayIcon = true

var body: some Scene {
// Menu bar presence — the primary UI for VocaMac
MenuBarExtra {
MenuBarExtra(isInserted: $showTrayIcon) {
MenuBarView(settingsManager: settingsManager, updateWindowManager: updateWindowManager)
.environmentObject(appState)
} label: {
Expand All @@ -244,6 +245,11 @@ struct VocaMacApp: App {
}
}
.menuBarExtraStyle(.window)

Settings {
SettingsView()
.environmentObject(appState)
}
}

@MainActor init() {
Expand Down
1 change: 1 addition & 0 deletions Sources/VocaMac/Models/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ final class AppState: ObservableObject {
@AppStorage(PreferenceKey.selectedLanguage) var selectedLanguage: String = "auto"
@AppStorage("vocamac.launchAtLogin") var launchAtLogin: Bool = false
@AppStorage("vocamac.preserveClipboard") var preserveClipboard: Bool = true
@AppStorage("vocamac.showTrayIcon") var showTrayIcon: Bool = true
@AppStorage("vocamac.soundEffectsEnabled") var soundEffectsEnabled: Bool = true
@AppStorage("vocamac.overlayStyle") var overlayStyle: OverlayStyle = .minimal
@AppStorage("vocamac.overlayPosition") var overlayPosition: OverlayPosition = .bottom
Expand Down
6 changes: 6 additions & 0 deletions Sources/VocaMac/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,12 @@ struct ApplicationSettingsPage: View {
Text("When enabled, your clipboard contents are restored after injecting text.")
.font(.caption)
.foregroundStyle(.secondary)

Toggle("Show VocaMac in the menu bar", isOn: $appState.showTrayIcon)

Text("Display the VocaMac icon in the system menu bar. If hidden, use ⌘, while VocaMac is active to reopen Settings.")
.font(.caption)
.foregroundStyle(.secondary)
}

Section("Recording Overlay") {
Expand Down
25 changes: 25 additions & 0 deletions Tests/VocaMacTests/AppStateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,31 @@ final class TranslationToggleTests: XCTestCase {
}
}

// MARK: - Tray Icon Toggle Tests

final class TrayIconToggleTests: XCTestCase {

@MainActor
func testTrayIconIsShownByDefault() {
let (appState, _) = AppState.makeTestState()

XCTAssertTrue(appState.showTrayIcon)
}

@MainActor
func testTrayIconCanBeToggled() {
let (appState, _) = AppState.makeTestState()

appState.showTrayIcon = false
XCTAssertFalse(appState.showTrayIcon)
XCTAssertFalse(UserDefaults.standard.bool(forKey: "vocamac.showTrayIcon"))

appState.showTrayIcon = true
XCTAssertTrue(appState.showTrayIcon)
XCTAssertTrue(UserDefaults.standard.bool(forKey: "vocamac.showTrayIcon"))
}
}


// MARK: - OnboardingStep Tests

Expand Down
1 change: 1 addition & 0 deletions Tests/VocaMacTests/Mocks/MockServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ extension AppState {
) -> (appState: AppState, mocks: TestMocks) {
UserDefaults.standard.removeObject(forKey: "vocamac.selectedAudioDeviceID")
UserDefaults.standard.removeObject(forKey: "vocamac.selectedAudioDeviceName")
UserDefaults.standard.removeObject(forKey: "vocamac.showTrayIcon")

let audioEngine = MockAudioEngine()
let soundManager = MockSoundManager()
Expand Down
1 change: 1 addition & 0 deletions docs/DATA_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ struct UserSettings {
// App Behavior
var launchAtLogin: Bool = false
var preserveClipboard: Bool = true // Restore clipboard after text injection
var showTrayIcon: Bool = true // Show VocaMac in the system menu bar
var playSoundEffects: Bool = false // Sound on start/stop recording
}
```
Expand Down
Loading