settings_accounts.go

  1package tui
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"charm.land/bubbles/v2/textinput"
  8	tea "charm.land/bubbletea/v2"
  9	"charm.land/lipgloss/v2"
 10	"github.com/floatpane/matcha/config"
 11)
 12
 13func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 14	if m.isCryptoConfig {
 15		var m2 *Settings
 16		var cmd tea.Cmd
 17		m2, cmd = m.updateSMIMEConfig(msg)
 18		return m2, cmd
 19	}
 20
 21	if m.confirmingDelete {
 22		switch msg.String() {
 23		case "y", "Y":
 24			if m.accountsCursor < len(m.cfg.Accounts) {
 25				accountID := m.cfg.Accounts[m.accountsCursor].ID
 26				m.confirmingDelete = false
 27				return m, func() tea.Msg {
 28					return DeleteAccountMsg{AccountID: accountID}
 29				}
 30			}
 31		case "n", "N", "esc":
 32			m.confirmingDelete = false
 33			return m, nil
 34		}
 35		return m, nil
 36	}
 37
 38	switch msg.String() {
 39	case "up", "k":
 40		itemCount := len(m.cfg.Accounts) + 1
 41		m.accountsCursor = (m.accountsCursor - 1 + itemCount) % itemCount
 42	case keyDown, "j":
 43		itemCount := len(m.cfg.Accounts) + 1
 44		m.accountsCursor = (m.accountsCursor + 1) % itemCount
 45	case "d":
 46		if m.accountsCursor < len(m.cfg.Accounts) && len(m.cfg.Accounts) > 0 {
 47			m.confirmingDelete = true
 48		}
 49	case "e":
 50		if m.accountsCursor < len(m.cfg.Accounts) {
 51			acc := m.cfg.Accounts[m.accountsCursor]
 52			return m, func() tea.Msg {
 53				return GoToEditAccountMsg{
 54					AccountID:    acc.ID,
 55					Provider:     acc.ServiceProvider,
 56					Name:         acc.Name,
 57					Email:        acc.Email,
 58					FetchEmail:   acc.FetchEmail,
 59					SendAsEmail:  acc.SendAsEmail,
 60					CatchAll:     acc.CatchAll,
 61					IMAPServer:   acc.IMAPServer,
 62					IMAPPort:     acc.IMAPPort,
 63					SMTPServer:   acc.SMTPServer,
 64					SMTPPort:     acc.SMTPPort,
 65					Insecure:     acc.Insecure,
 66					Protocol:     acc.Protocol,
 67					JMAPEndpoint: acc.JMAPEndpoint,
 68					POP3Server:   acc.POP3Server,
 69					POP3Port:     acc.POP3Port,
 70					MaildirPath:  acc.MaildirPath,
 71				}
 72			}
 73		}
 74	case "s": // Edit account signature
 75		if m.accountsCursor < len(m.cfg.Accounts) {
 76			return m, func() tea.Msg { return GoToSignatureEditorMsg{AccountID: m.cfg.Accounts[m.accountsCursor].ID} }
 77		}
 78	case "c": // Quick shortcut to crypto config
 79		if m.accountsCursor < len(m.cfg.Accounts) {
 80			m.enterCryptoConfig()
 81			return m, textinput.Blink
 82		}
 83	case keyEnter:
 84		if m.accountsCursor == len(m.cfg.Accounts) {
 85			return m, func() tea.Msg { return GoToAddAccountMsg{} }
 86		} else if m.accountsCursor < len(m.cfg.Accounts) {
 87			m.enterCryptoConfig()
 88			return m, textinput.Blink
 89		}
 90	}
 91	return m, nil
 92}
 93
 94func (m *Settings) enterCryptoConfig() {
 95	m.isCryptoConfig = true
 96	m.editingAccountIdx = m.accountsCursor
 97	acc := m.cfg.Accounts[m.accountsCursor]
 98
 99	m.smimeCertInput.SetValue(acc.SMIMECert)
100	m.smimeKeyInput.SetValue(acc.SMIMEKey)
101	m.pgpPublicKeyInput.SetValue(acc.PGPPublicKey)
102	m.pgpPrivateKeyInput.SetValue(acc.PGPPrivateKey)
103	if acc.PGPKeySource == "" {
104		m.pgpKeySource = "file"
105	} else {
106		m.pgpKeySource = acc.PGPKeySource
107	}
108	m.pgpPINInput.SetValue(acc.PGPPIN)
109
110	m.cryptoFocusIndex = 0
111	m.smimeCertInput.Focus()
112	m.smimeKeyInput.Blur()
113	m.pgpPublicKeyInput.Blur()
114	m.pgpPrivateKeyInput.Blur()
115	m.pgpPINInput.Blur()
116}
117
118func (m *Settings) viewAccounts() string {
119	if m.isCryptoConfig {
120		return m.viewSMIMEConfig()
121	}
122
123	var b strings.Builder
124	b.WriteString(titleStyle.Render(t("settings_accounts.title")) + "\n\n")
125
126	if len(m.cfg.Accounts) == 0 {
127		b.WriteString(accountEmailStyle.Render("  " + t("settings_accounts.no_accounts") + "\n\n"))
128	}
129
130	for i, account := range m.cfg.Accounts {
131		displayName := account.Email
132		if account.Name != "" {
133			displayName = fmt.Sprintf("%s (%s)", account.Name, account.FetchEmail)
134		}
135
136		providerInfo := account.ServiceProvider
137		if account.ServiceProvider == "custom" {
138			providerInfo = fmt.Sprintf("custom: %s", account.IMAPServer)
139		}
140
141		if account.SMIMECert != "" && account.SMIMEKey != "" {
142			providerInfo += " [S/MIME Configured]"
143		}
144		if account.PGPPublicKey != "" && account.PGPPrivateKey != "" {
145			providerInfo += " [PGP Configured]"
146		}
147		if config.HasAccountSignature(&account) {
148			providerInfo += " [Signature]"
149		}
150		if account.CatchAll {
151			providerInfo += " [Catch-All]"
152		}
153
154		line := fmt.Sprintf("%s - %s", displayName, accountEmailStyle.Render(providerInfo))
155
156		selected := m.accountsCursor == i
157		cursor := m.contentCursor(selected)
158		style := m.contentItemStyle(selected)
159
160		b.WriteString(style.Render(cursor+line) + "\n")
161	}
162
163	// Add Account option
164	selected := m.accountsCursor == len(m.cfg.Accounts)
165	cursor := m.contentCursor(selected)
166	style := m.contentItemStyle(selected)
167	b.WriteString(style.Render(cursor+t("settings_accounts.add_account")) + "\n\n")
168
169	b.WriteString(helpStyle.Render(t("settings_accounts.help")))
170
171	if m.confirmingDelete {
172		accountName := m.cfg.Accounts[m.accountsCursor].Email
173		dialog := DialogBoxStyle.Render(
174			lipgloss.JoinVertical(lipgloss.Center,
175				dangerStyle.Render("Delete account?"),
176				accountEmailStyle.Render(accountName),
177				HelpStyle.Render("\n(y/n)"),
178			),
179		)
180		// Try to overlay dialog in a reasonable way, since we don't have full screen width access easily here.
181		// Just append it.
182		b.WriteString("\n\n" + dialog)
183	}
184
185	return b.String()
186}