1package tui
2
3import (
4 "fmt"
5 "strings"
6
7 tea "charm.land/bubbletea/v2"
8 "github.com/floatpane/matcha/config"
9)
10
11const cryptoConfigMaxFocus = 9
12
13func (m *Settings) updateSMIMEConfig(msg tea.KeyPressMsg) (*Settings, tea.Cmd) {
14 key := msg.Key()
15 isEnter := key.Code == tea.KeyEnter || key.Code == tea.KeyReturn || key.Code == tea.KeyKpEnter
16 isSpace := key.Code == tea.KeySpace
17
18 switch msg.String() {
19 case "esc":
20 m.isCryptoConfig = false
21 return m, nil
22 case "tab", keyShiftTab, "up", keyDown:
23 return m, m.cryptoNavigate(msg.String())
24 }
25
26 if isEnter {
27 return m, m.cryptoHandleEnter()
28 }
29 if isSpace && m.cryptoToggle() {
30 return m, nil
31 }
32
33 // Forward any remaining key press (typing, backspace, paste, cursor) to the
34 // focused text input.
35 return m, m.cryptoForwardInput(msg)
36}
37
38// cryptoSetFocus moves focus to index next, blurring every input and focusing
39// the one (if any) bound to that row.
40func (m *Settings) cryptoSetFocus(next int) tea.Cmd {
41 m.cryptoFocusIndex = next
42 m.smimeCertInput.Blur()
43 m.smimeKeyInput.Blur()
44 m.pgpPublicKeyInput.Blur()
45 m.pgpPrivateKeyInput.Blur()
46 m.pgpPINInput.Blur()
47
48 switch m.cryptoFocusIndex {
49 case 0:
50 return m.smimeCertInput.Focus()
51 case 1:
52 return m.smimeKeyInput.Focus()
53 case 3:
54 return m.pgpPublicKeyInput.Focus()
55 case 4:
56 return m.pgpPrivateKeyInput.Focus()
57 case 6:
58 return m.pgpPINInput.Focus()
59 }
60 return nil
61}
62
63// cryptoNavigate handles tab/shift-tab/up/down, skipping the YubiKey PIN row
64// (index 6) when the key source is not a YubiKey.
65func (m *Settings) cryptoNavigate(s string) tea.Cmd {
66 back := s == keyShiftTab || s == "up"
67 if back {
68 m.cryptoFocusIndex--
69 if m.cryptoFocusIndex < 0 {
70 m.cryptoFocusIndex = cryptoConfigMaxFocus
71 }
72 } else {
73 m.cryptoFocusIndex++
74 if m.cryptoFocusIndex > cryptoConfigMaxFocus {
75 m.cryptoFocusIndex = 0
76 }
77 }
78 if m.cryptoFocusIndex == 6 && m.pgpKeySource != keyYubikey {
79 if back {
80 m.cryptoFocusIndex = 5
81 } else {
82 m.cryptoFocusIndex = 7
83 }
84 }
85 return m.cryptoSetFocus(m.cryptoFocusIndex)
86}
87
88// cryptoHandleEnter saves (row 8), cancels (row 9), or advances focus.
89func (m *Settings) cryptoHandleEnter() tea.Cmd {
90 switch m.cryptoFocusIndex {
91 case 8: // Save
92 acct := &m.cfg.Accounts[m.editingAccountIdx]
93 acct.SMIMECert = m.smimeCertInput.Value()
94 acct.SMIMEKey = m.smimeKeyInput.Value()
95 acct.PGPPublicKey = m.pgpPublicKeyInput.Value()
96 acct.PGPPrivateKey = m.pgpPrivateKeyInput.Value()
97 acct.PGPKeySource = m.pgpKeySource
98 acct.PGPPIN = m.pgpPINInput.Value()
99 _ = config.SaveConfig(m.cfg)
100 m.isCryptoConfig = false
101 return nil
102 case 9: // Cancel
103 m.isCryptoConfig = false
104 return nil
105 default:
106 next := m.cryptoFocusIndex + 1
107 if next == 6 && m.pgpKeySource != keyYubikey {
108 next = 7
109 }
110 return m.cryptoSetFocus(next)
111 }
112}
113
114// cryptoToggle flips the boolean/choice rows (Sign By Default, Key Source).
115// It reports whether the current row was a toggle.
116func (m *Settings) cryptoToggle() bool {
117 acct := &m.cfg.Accounts[m.editingAccountIdx]
118 switch m.cryptoFocusIndex {
119 case 2:
120 acct.SMIMESignByDefault = !acct.SMIMESignByDefault
121 case 5:
122 if m.pgpKeySource == "file" {
123 m.pgpKeySource = keyYubikey
124 } else {
125 m.pgpKeySource = "file"
126 }
127 case 7:
128 acct.PGPSignByDefault = !acct.PGPSignByDefault
129 default:
130 return false
131 }
132 return true
133}
134
135// cryptoForwardInput routes a key press to the focused text input. The
136// toggle/button rows (2, 5, 7, 8, 9) have no input and are no-ops.
137func (m *Settings) cryptoForwardInput(msg tea.KeyPressMsg) tea.Cmd {
138 var cmd tea.Cmd
139 switch m.cryptoFocusIndex {
140 case 0:
141 m.smimeCertInput, cmd = m.smimeCertInput.Update(msg)
142 case 1:
143 m.smimeKeyInput, cmd = m.smimeKeyInput.Update(msg)
144 case 3:
145 m.pgpPublicKeyInput, cmd = m.pgpPublicKeyInput.Update(msg)
146 case 4:
147 m.pgpPrivateKeyInput, cmd = m.pgpPrivateKeyInput.Update(msg)
148 case 6:
149 m.pgpPINInput, cmd = m.pgpPINInput.Update(msg)
150 }
151 return cmd
152}
153
154func (m *Settings) viewSMIMEConfig() string {
155 var b strings.Builder
156 account := m.cfg.Accounts[m.editingAccountIdx]
157 b.WriteString(titleStyle.Render(fmt.Sprintf("Crypto Config: %s", account.FetchEmail)) + "\n\n")
158
159 renderField := func(index int, label, content string) {
160 if m.cryptoFocusIndex == index {
161 b.WriteString(m.contentFocusStyle().Render(label) + "\n")
162 } else {
163 b.WriteString(settingsBlurredStyle.Render(label) + "\n")
164 }
165 b.WriteString(content + "\n\n")
166 }
167
168 // S/MIME
169 b.WriteString(settingsFocusedStyle.Render("S/MIME") + "\n")
170 renderField(0, "Certificate (PEM) Path:", m.smimeCertInput.View())
171 renderField(1, "Private Key (PEM) Path:", m.smimeKeyInput.View())
172 smimeSign := "OFF"
173 if account.SMIMESignByDefault {
174 smimeSign = "ON"
175 }
176 renderField(2, "Sign By Default:", smimeSign)
177
178 // PGP
179 b.WriteString(settingsFocusedStyle.Render("PGP") + "\n")
180 renderField(3, "Public Key Path:", m.pgpPublicKeyInput.View())
181 renderField(4, "Private Key Path:", m.pgpPrivateKeyInput.View())
182
183 keySource := "File"
184 if m.pgpKeySource == keyYubikey {
185 keySource = "YubiKey"
186 }
187 renderField(5, "Key Source:", keySource)
188
189 if m.pgpKeySource == keyYubikey {
190 renderField(6, "YubiKey PIN:", m.pgpPINInput.View())
191 }
192
193 pgpSign := "OFF"
194 if account.PGPSignByDefault {
195 pgpSign = "ON"
196 }
197 renderField(7, "Sign By Default:", pgpSign)
198
199 saveBtn := "[ Save ]"
200 cancelBtn := "[ Cancel ]"
201 if m.cryptoFocusIndex == 8 {
202 saveBtn = m.contentFocusStyle().Render(saveBtn)
203 } else {
204 saveBtn = settingsBlurredStyle.Render(saveBtn)
205 }
206 if m.cryptoFocusIndex == 9 {
207 cancelBtn = m.contentFocusStyle().Render(cancelBtn)
208 } else {
209 cancelBtn = settingsBlurredStyle.Render(cancelBtn)
210 }
211
212 b.WriteString(saveBtn + " " + cancelBtn + "\n\n")
213 b.WriteString(helpStyle.Render("tab: next • enter: next/save • space: toggle • esc: cancel"))
214
215 return b.String()
216}