Detailed changes
@@ -46,11 +46,12 @@ type MailingList struct {
// Config stores the user's email configuration with multiple accounts.
type Config struct {
- Accounts []Account `json:"accounts"`
- DisableImages bool `json:"disable_images,omitempty"`
- HideTips bool `json:"hide_tips,omitempty"`
- Theme string `json:"theme,omitempty"`
- MailingLists []MailingList `json:"mailing_lists,omitempty"`
+ Accounts []Account `json:"accounts"`
+ DisableImages bool `json:"disable_images,omitempty"`
+ HideTips bool `json:"hide_tips,omitempty"`
+ DisableNotifications bool `json:"disable_notifications,omitempty"`
+ Theme string `json:"theme,omitempty"`
+ MailingLists []MailingList `json:"mailing_lists,omitempty"`
}
// GetIMAPServer returns the IMAP server address for the account.
@@ -192,11 +193,12 @@ func LoadConfig() (*Config, error) {
AuthMethod string `json:"auth_method,omitempty"`
}
type diskConfig struct {
- Accounts []rawAccount `json:"accounts"`
- DisableImages bool `json:"disable_images,omitempty"`
- HideTips bool `json:"hide_tips,omitempty"`
- Theme string `json:"theme,omitempty"`
- MailingLists []MailingList `json:"mailing_lists,omitempty"`
+ Accounts []rawAccount `json:"accounts"`
+ DisableImages bool `json:"disable_images,omitempty"`
+ HideTips bool `json:"hide_tips,omitempty"`
+ DisableNotifications bool `json:"disable_notifications,omitempty"`
+ Theme string `json:"theme,omitempty"`
+ MailingLists []MailingList `json:"mailing_lists,omitempty"`
}
var raw diskConfig
@@ -226,6 +228,7 @@ func LoadConfig() (*Config, error) {
config.DisableImages = raw.DisableImages
config.HideTips = raw.HideTips
+ config.DisableNotifications = raw.DisableNotifications
config.Theme = raw.Theme
config.MailingLists = raw.MailingLists
for _, rawAcc := range raw.Accounts {
@@ -23,6 +23,7 @@ import (
"github.com/floatpane/matcha/clib"
"github.com/floatpane/matcha/config"
"github.com/floatpane/matcha/fetcher"
+ "github.com/floatpane/matcha/notify"
"github.com/floatpane/matcha/plugin"
"github.com/floatpane/matcha/sender"
"github.com/floatpane/matcha/theme"
@@ -485,6 +486,17 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, fetchFolderEmailsCmd(m.config, m.folderInbox.GetCurrentFolder())
case tui.IdleNewMailMsg:
+ // Send desktop notification for new mail (if enabled)
+ if m.config == nil || !m.config.DisableNotifications {
+ accountName := msg.AccountID
+ if m.config != nil {
+ if acc := m.config.GetAccountByID(msg.AccountID); acc != nil {
+ accountName = acc.Email
+ }
+ }
+ go notify.Send("Matcha", fmt.Sprintf("New mail in %s (%s)", msg.FolderName, accountName))
+ }
+
// IDLE detected new mail — refetch the folder if we're viewing it
if m.folderInbox != nil && m.folderInbox.GetCurrentFolder() == msg.FolderName {
return m, tea.Batch(
@@ -0,0 +1,21 @@
+package notify
+
+import (
+ "fmt"
+ "os/exec"
+ "runtime"
+)
+
+// Send delivers a desktop notification with the given title and body.
+// On macOS it uses osascript; on Linux it uses notify-send.
+func Send(title, body string) error {
+ switch runtime.GOOS {
+ case "darwin":
+ script := fmt.Sprintf(`display notification %q with title %q sound name "default"`, body, title)
+ return exec.Command("osascript", "-e", script).Run()
+ case "linux":
+ return exec.Command("notify-send", title, body).Run()
+ default:
+ return nil
+ }
+}
@@ -128,8 +128,8 @@ func (m *Settings) updateMain(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
m.cursor--
}
case "down", "j":
- // Options: 0: Email Accounts, 1: Theme, 2: Image Display, 3: Edit Signature, 4: Contextual Tips, 5: Mailing Lists
- if m.cursor < 5 {
+ // Options: 0: Email Accounts, 1: Theme, 2: Image Display, 3: Edit Signature, 4: Contextual Tips, 5: Desktop Notifications, 6: Mailing Lists
+ if m.cursor < 6 {
m.cursor++
}
case "enter":
@@ -160,7 +160,11 @@ func (m *Settings) updateMain(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
m.cfg.HideTips = !m.cfg.HideTips
_ = config.SaveConfig(m.cfg)
return m, nil
- case 5: // Mailing Lists
+ case 5: // Desktop Notifications
+ m.cfg.DisableNotifications = !m.cfg.DisableNotifications
+ _ = config.SaveConfig(m.cfg)
+ return m, nil
+ case 6: // Mailing Lists
m.state = SettingsMailingLists
m.cursor = 0
return m, nil
@@ -443,9 +447,22 @@ func (m *Settings) viewMain() string {
}
b.WriteString("\n")
- // Option 5: Mailing Lists
- mailingListsText := "Mailing Lists"
+ // Option 5: Desktop Notifications
+ notifStatus := "ON"
+ if m.cfg.DisableNotifications {
+ notifStatus = "OFF"
+ }
+ notifText := fmt.Sprintf("Desktop Notifications: %s", notifStatus)
if m.cursor == 5 {
+ b.WriteString(selectedAccountItemStyle.Render("> " + notifText))
+ } else {
+ b.WriteString(accountItemStyle.Render(" " + notifText))
+ }
+ b.WriteString("\n")
+
+ // Option 6: Mailing Lists
+ mailingListsText := "Mailing Lists"
+ if m.cursor == 6 {
b.WriteString(selectedAccountItemStyle.Render("> " + mailingListsText))
} else {
b.WriteString(accountItemStyle.Render(" " + mailingListsText))
@@ -466,6 +483,8 @@ func (m *Settings) viewMain() string {
case 4:
tip = "Toggle displaying helpful contextual tips like this one."
case 5:
+ tip = "Toggle desktop notifications when new mail arrives."
+ case 6:
tip = "Manage groups of email addresses to quickly send to multiple people."
}
if tip != "" {