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 "down", "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				}
 71			}
 72		}
 73	case "s": // Edit account signature
 74		if m.accountsCursor < len(m.cfg.Accounts) {
 75			return m, func() tea.Msg { return GoToSignatureEditorMsg{AccountID: m.cfg.Accounts[m.accountsCursor].ID} }
 76		}
 77	case "c": // Quick shortcut to crypto config
 78		if m.accountsCursor < len(m.cfg.Accounts) {
 79			m.enterCryptoConfig()
 80			return m, textinput.Blink
 81		}
 82	case "enter":
 83		if m.accountsCursor == len(m.cfg.Accounts) {
 84			return m, func() tea.Msg { return GoToAddAccountMsg{} }
 85		} else if m.accountsCursor < len(m.cfg.Accounts) {
 86			m.enterCryptoConfig()
 87			return m, textinput.Blink
 88		}
 89	}
 90	return m, nil
 91}
 92
 93func (m *Settings) enterCryptoConfig() {
 94	m.isCryptoConfig = true
 95	m.editingAccountIdx = m.accountsCursor
 96	acc := m.cfg.Accounts[m.accountsCursor]
 97
 98	m.smimeCertInput.SetValue(acc.SMIMECert)
 99	m.smimeKeyInput.SetValue(acc.SMIMEKey)
100	m.pgpPublicKeyInput.SetValue(acc.PGPPublicKey)
101	m.pgpPrivateKeyInput.SetValue(acc.PGPPrivateKey)
102	if acc.PGPKeySource == "" {
103		m.pgpKeySource = "file"
104	} else {
105		m.pgpKeySource = acc.PGPKeySource
106	}
107	m.pgpPINInput.SetValue(acc.PGPPIN)
108
109	m.cryptoFocusIndex = 0
110	m.smimeCertInput.Focus()
111	m.smimeKeyInput.Blur()
112	m.pgpPublicKeyInput.Blur()
113	m.pgpPrivateKeyInput.Blur()
114	m.pgpPINInput.Blur()
115}
116
117func (m *Settings) viewAccounts() string {
118	if m.isCryptoConfig {
119		return m.viewSMIMEConfig()
120	}
121
122	var b strings.Builder
123	b.WriteString(titleStyle.Render(t("settings_accounts.title")) + "\n\n")
124
125	if len(m.cfg.Accounts) == 0 {
126		b.WriteString(accountEmailStyle.Render("  " + t("settings_accounts.no_accounts") + "\n\n"))
127	}
128
129	for i, account := range m.cfg.Accounts {
130		displayName := account.Email
131		if account.Name != "" {
132			displayName = fmt.Sprintf("%s (%s)", account.Name, account.FetchEmail)
133		}
134
135		providerInfo := account.ServiceProvider
136		if account.ServiceProvider == "custom" {
137			providerInfo = fmt.Sprintf("custom: %s", account.IMAPServer)
138		}
139
140		if account.SMIMECert != "" && account.SMIMEKey != "" {
141			providerInfo += " [S/MIME Configured]"
142		}
143		if account.PGPPublicKey != "" && account.PGPPrivateKey != "" {
144			providerInfo += " [PGP Configured]"
145		}
146		if config.HasAccountSignature(&account) {
147			providerInfo += " [Signature]"
148		}
149		if account.CatchAll {
150			providerInfo += " [Catch-All]"
151		}
152
153		line := fmt.Sprintf("%s - %s", displayName, accountEmailStyle.Render(providerInfo))
154
155		cursor := "  "
156		style := accountItemStyle
157		if m.accountsCursor == i {
158			cursor = "> "
159			style = selectedAccountItemStyle
160		}
161
162		b.WriteString(style.Render(cursor+line) + "\n")
163	}
164
165	// Add Account option
166	cursor := "  "
167	style := accountItemStyle
168	if m.accountsCursor == len(m.cfg.Accounts) {
169		cursor = "> "
170		style = selectedAccountItemStyle
171	}
172	b.WriteString(style.Render(cursor+t("settings_accounts.add_account")) + "\n\n")
173
174	b.WriteString(helpStyle.Render(t("settings_accounts.help")))
175
176	if m.confirmingDelete {
177		accountName := m.cfg.Accounts[m.accountsCursor].Email
178		dialog := DialogBoxStyle.Render(
179			lipgloss.JoinVertical(lipgloss.Center,
180				dangerStyle.Render("Delete account?"),
181				accountEmailStyle.Render(accountName),
182				HelpStyle.Render("\n(y/n)"),
183			),
184		)
185		// Try to overlay dialog in a reasonable way, since we don't have full screen width access easily here.
186		// Just append it.
187		b.WriteString("\n\n" + dialog)
188	}
189
190	return b.String()
191}