Skip to content
Merged
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
5 changes: 5 additions & 0 deletions cmd/goose/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ type App struct {
hideStaleIncoming bool
loadingTurnData bool
enableReminders bool // Whether to send daily reminder notifications
enableAudioCues bool // Whether to play audio cues for notifications
}

func main() {
Expand Down Expand Up @@ -183,8 +184,12 @@ func main() {
updateInterval: updateInterval,
pendingTurnResults: make([]TurnResult, 0),
enableReminders: true,
enableAudioCues: true,
}

// Load saved settings
app.loadSettings()

log.Println("Initializing GitHub clients...")
err = app.initClients(ctx)
if err != nil {
Expand Down
107 changes: 107 additions & 0 deletions cmd/goose/settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Package main - settings.go provides persistent settings storage.
package main

import (
"encoding/json"
"log"
"os"
"path/filepath"
)

// Settings represents persistent user settings.
type Settings struct {
EnableAudioCues bool `json:"enable_audio_cues"`
EnableReminders bool `json:"enable_reminders"`
HideStale bool `json:"hide_stale"`
}

// getSettingsDir returns the configuration directory for settings.
func getSettingsDir() (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(configDir, "ready-to-review"), nil
}

// loadSettings loads settings from disk or returns defaults.
func (app *App) loadSettings() {
settingsDir, err := getSettingsDir()
if err != nil {
log.Printf("Failed to get settings directory: %v", err)
// Use defaults
app.enableAudioCues = true
app.enableReminders = true
app.hideStaleIncoming = true
return
}

settingsPath := filepath.Join(settingsDir, "settings.json")

data, err := os.ReadFile(settingsPath)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("Failed to read settings: %v", err)
}
// Use defaults
app.enableAudioCues = true
app.enableReminders = true
app.hideStaleIncoming = true
return
}

var settings Settings
if err := json.Unmarshal(data, &settings); err != nil {
log.Printf("Failed to parse settings: %v", err)
// Use defaults
app.enableAudioCues = true
app.enableReminders = true
app.hideStaleIncoming = true
return
}

app.enableAudioCues = settings.EnableAudioCues
app.enableReminders = settings.EnableReminders
app.hideStaleIncoming = settings.HideStale
log.Printf("Loaded settings: audio_cues=%v, reminders=%v, hide_stale=%v",
app.enableAudioCues, app.enableReminders, app.hideStaleIncoming)
}

// saveSettings saves current settings to disk.
func (app *App) saveSettings() {
settingsDir, err := getSettingsDir()
if err != nil {
log.Printf("Failed to get settings directory: %v", err)
return
}

app.mu.RLock()
settings := Settings{
EnableAudioCues: app.enableAudioCues,
EnableReminders: app.enableReminders,
HideStale: app.hideStaleIncoming,
}
app.mu.RUnlock()

// Ensure directory exists
if err := os.MkdirAll(settingsDir, 0o700); err != nil {
log.Printf("Failed to create settings directory: %v", err)
return
}

settingsPath := filepath.Join(settingsDir, "settings.json")

data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
log.Printf("Failed to marshal settings: %v", err)
return
}

if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
log.Printf("Failed to save settings: %v", err)
return
}

log.Printf("Saved settings: audio_cues=%v, reminders=%v, hide_stale=%v",
settings.EnableAudioCues, settings.EnableReminders, settings.HideStale)
}
10 changes: 10 additions & 0 deletions cmd/goose/sound.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ func (app *App) initSoundCache() {

// playSound plays a cached sound file using platform-specific commands.
func (app *App) playSound(ctx context.Context, soundType string) {
// Check if audio cues are enabled
app.mu.RLock()
audioEnabled := app.enableAudioCues
app.mu.RUnlock()

if !audioEnabled {
log.Printf("[SOUND] Sound playback skipped (audio cues disabled): %s", soundType)
return
}

log.Printf("[SOUND] Playing %s sound", soundType)
// Ensure sounds are cached
app.initSoundCache()
Expand Down
45 changes: 40 additions & 5 deletions cmd/goose/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,18 +287,24 @@ func (app *App) addStaticMenuItems(ctx context.Context) {
hideStaleItem.Check()
}
hideStaleItem.Click(func() {
app.mu.Lock()
app.hideStaleIncoming = !app.hideStaleIncoming
if app.hideStaleIncoming {
hideStale := app.hideStaleIncoming
// Clear menu state to force rebuild
app.lastMenuState = nil
app.mu.Unlock()

if hideStale {
hideStaleItem.Check()
} else {
hideStaleItem.Uncheck()
}

// Save settings to disk
app.saveSettings()

// Toggle hide stale PRs setting
// Force menu rebuild since hideStaleIncoming changed
app.mu.Lock()
// Clear menu state to force rebuild
app.lastMenuState = nil
app.mu.Unlock()
app.rebuildMenu(ctx)
})

Expand All @@ -323,11 +329,40 @@ func (app *App) addStaticMenuItems(ctx context.Context) {
reminderItem.Uncheck()
log.Println("[SETTINGS] Daily reminders disabled")
}

// Save settings to disk
app.saveSettings()
})

// Add login item option (macOS only)
addLoginItemUI(ctx, app)

// Audio cues
// Add 'Audio cues' option
audioItem := systray.AddMenuItem("Audio cues", "Play sounds for notifications")
app.mu.RLock()
if app.enableAudioCues {
audioItem.Check()
}
app.mu.RUnlock()
audioItem.Click(func() {
app.mu.Lock()
app.enableAudioCues = !app.enableAudioCues
enabled := app.enableAudioCues
app.mu.Unlock()

if enabled {
audioItem.Check()
log.Println("[SETTINGS] Audio cues enabled")
} else {
audioItem.Uncheck()
log.Println("[SETTINGS] Audio cues disabled")
}

// Save settings to disk
app.saveSettings()
})

// Quit
// Add 'Quit' option
quitItem := systray.AddMenuItem("Quit", "")
Expand Down
Loading