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)
 11
 12func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 13	if m.isCryptoConfig {
 14		var m2 *Settings
 15		var cmd tea.Cmd
 16		m2, cmd = m.updateSMIMEConfig(msg)
 17		return m2, cmd
 18	}
 19
 20	if m.confirmingDelete {
 21		switch msg.String() {
 22		case "y", "Y":
 23			if m.accountsCursor < len(m.cfg.Accounts) {
 24				accountID := m.cfg.Accounts[m.accountsCursor].ID
 25				m.confirmingDelete = false
 26				return m, func() tea.Msg {
 27					return DeleteAccountMsg{AccountID: accountID}
 28				}
 29			}
 30		case "n", "N", "esc":
 31			m.confirmingDelete = false
 32			return m, nil
 33		}
 34		return m, nil
 35	}
 36
 37	switch msg.String() {
 38	case "up", "k":
 39		if m.accountsCursor > 0 {
 40			m.accountsCursor--
 41		}
 42	case "down", "j":
 43		if m.accountsCursor < len(m.cfg.Accounts) {
 44			m.accountsCursor++
 45		}
 46	case "d":
 47		if m.accountsCursor < len(m.cfg.Accounts) && len(m.cfg.Accounts) > 0 {
 48			m.confirmingDelete = true
 49		}
 50	case "e":
 51		if m.accountsCursor < len(m.cfg.Accounts) {
 52			acc := m.cfg.Accounts[m.accountsCursor]
 53			return m, func() tea.Msg {
 54				return GoToEditAccountMsg{
 55					AccountID:    acc.ID,
 56					Provider:     acc.ServiceProvider,
 57					Name:         acc.Name,
 58					Email:        acc.Email,
 59					FetchEmail:   acc.FetchEmail,
 60					SendAsEmail:  acc.SendAsEmail,
 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 "c": // Quick shortcut to crypto config
 74		if m.accountsCursor < len(m.cfg.Accounts) {
 75			m.enterCryptoConfig()
 76			return m, textinput.Blink
 77		}
 78	case "enter":
 79		if m.accountsCursor == len(m.cfg.Accounts) {
 80			return m, func() tea.Msg { return GoToAddAccountMsg{} }
 81		} else if m.accountsCursor < len(m.cfg.Accounts) {
 82			m.enterCryptoConfig()
 83			return m, textinput.Blink
 84		}
 85	}
 86	return m, nil
 87}
 88
 89func (m *Settings) enterCryptoConfig() {
 90	m.isCryptoConfig = true
 91	m.editingAccountIdx = m.accountsCursor
 92	acc := m.cfg.Accounts[m.accountsCursor]
 93
 94	m.smimeCertInput.SetValue(acc.SMIMECert)
 95	m.smimeKeyInput.SetValue(acc.SMIMEKey)
 96	m.pgpPublicKeyInput.SetValue(acc.PGPPublicKey)
 97	m.pgpPrivateKeyInput.SetValue(acc.PGPPrivateKey)
 98	if acc.PGPKeySource == "" {
 99		m.pgpKeySource = "file"
100	} else {
101		m.pgpKeySource = acc.PGPKeySource
102	}
103	m.pgpPINInput.SetValue(acc.PGPPIN)
104
105	m.cryptoFocusIndex = 0
106	m.smimeCertInput.Focus()
107	m.smimeKeyInput.Blur()
108	m.pgpPublicKeyInput.Blur()
109	m.pgpPrivateKeyInput.Blur()
110	m.pgpPINInput.Blur()
111}
112
113func (m *Settings) viewAccounts() string {
114	if m.isCryptoConfig {
115		return m.viewSMIMEConfig()
116	}
117
118	var b strings.Builder
119	b.WriteString(titleStyle.Render("Account Settings") + "\n\n")
120
121	if len(m.cfg.Accounts) == 0 {
122		b.WriteString(accountEmailStyle.Render("  No accounts configured.\n\n"))
123	}
124
125	for i, account := range m.cfg.Accounts {
126		displayName := account.Email
127		if account.Name != "" {
128			displayName = fmt.Sprintf("%s (%s)", account.Name, account.FetchEmail)
129		}
130
131		providerInfo := account.ServiceProvider
132		if account.ServiceProvider == "custom" {
133			providerInfo = fmt.Sprintf("custom: %s", account.IMAPServer)
134		}
135
136		if account.SMIMECert != "" && account.SMIMEKey != "" {
137			providerInfo += " [S/MIME Configured]"
138		}
139		if account.PGPPublicKey != "" && account.PGPPrivateKey != "" {
140			providerInfo += " [PGP Configured]"
141		}
142
143		line := fmt.Sprintf("%s - %s", displayName, accountEmailStyle.Render(providerInfo))
144
145		cursor := "  "
146		style := accountItemStyle
147		if m.accountsCursor == i {
148			cursor = "> "
149			style = selectedAccountItemStyle
150		}
151
152		b.WriteString(style.Render(cursor+line) + "\n")
153	}
154
155	// Add Account option
156	cursor := "  "
157	style := accountItemStyle
158	if m.accountsCursor == len(m.cfg.Accounts) {
159		cursor = "> "
160		style = selectedAccountItemStyle
161	}
162	b.WriteString(style.Render(cursor+"Add New Account") + "\n\n")
163
164	b.WriteString(helpStyle.Render("↑/↓: navigate • enter: edit crypto config • e: edit server • d: delete"))
165
166	if m.confirmingDelete {
167		accountName := m.cfg.Accounts[m.accountsCursor].Email
168		dialog := DialogBoxStyle.Render(
169			lipgloss.JoinVertical(lipgloss.Center,
170				dangerStyle.Render("Delete account?"),
171				accountEmailStyle.Render(accountName),
172				HelpStyle.Render("\n(y/n)"),
173			),
174		)
175		// Try to overlay dialog in a reasonable way, since we don't have full screen width access easily here.
176		// Just append it.
177		b.WriteString("\n\n" + dialog)
178	}
179
180	return b.String()
181}