1package tui
2
3import (
4 "fmt"
5 "strings"
6
7 tea "charm.land/bubbletea/v2"
8 "github.com/floatpane/matcha/config"
9 "github.com/floatpane/matcha/i18n"
10)
11
12type generalOption struct {
13 labelKey string
14 value string
15 tip string
16 isAccountSig bool
17 accountID string
18}
19
20func (m *Settings) buildGeneralOptions() []generalOption {
21 opts := []generalOption{
22 {"settings_general.disable_images", onOff(m.cfg.DisableImages), "Prevent images from loading automatically in emails.", false, ""},
23 {"settings_general.hide_tips", onOff(m.cfg.HideTips), "Hide helpful hints displayed at the bottom of the screen.", false, ""},
24 {"settings_general.disable_notifications", onOff(m.cfg.DisableNotifications), "Turn off desktop notifications for new mail.", false, ""},
25 {"settings_general.enable_split_pane", onOff(m.cfg.EnableSplitPane), "View inbox and email side-by-side.", false, ""},
26 {"settings_general.enable_threaded", onOff(m.cfg.EnableThreaded), "Group emails into conversations by reply chain. Per-folder overrides are kept.", false, ""},
27 {"settings_general.date_format", getDateFormatLabel(m.cfg.DateFormat), "Change how dates and times are displayed.", false, ""},
28 {"settings_general.language", getLanguageLabel(m.cfg.GetLanguage()), "Change the interface language. Changes apply instantly.", false, ""},
29 {"settings_general.signature", getSignatureStatus(), "Configure the global signature appended to your outgoing emails.", false, ""},
30 }
31
32 for _, acc := range m.cfg.Accounts {
33 status := t("settings_general.signature_not_configured")
34 accCopy := acc // capture for pointer safety
35 if config.HasAccountSignature(&accCopy) {
36 status = t("settings_general.signature_configured")
37 }
38 opts = append(opts, generalOption{
39 labelKey: fmt.Sprintf("Signature (%s)", acc.Email),
40 value: status,
41 tip: fmt.Sprintf("Configure the signature for %s", acc.Email),
42 isAccountSig: true,
43 accountID: acc.ID,
44 })
45 }
46
47 return opts
48}
49
50func (m *Settings) updateGeneral(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
51 opts := m.buildGeneralOptions()
52
53 switch msg.String() {
54 case "up", "k":
55 m.generalCursor = (m.generalCursor - 1 + len(opts)) % len(opts)
56 case "down", "j":
57 m.generalCursor = (m.generalCursor + 1) % len(opts)
58 case "enter", "space", "right", "l":
59 if m.generalCursor < len(opts) {
60 opt := opts[m.generalCursor]
61 if opt.isAccountSig {
62 if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" {
63 return m, func() tea.Msg { return GoToSignatureEditorMsg{AccountID: opt.accountID} }
64 }
65 return m, nil
66 }
67
68 saved := false
69 switch m.generalCursor {
70 case 0: // Image Display
71 m.cfg.DisableImages = !m.cfg.DisableImages
72 _ = config.SaveConfig(m.cfg)
73 saved = true
74 case 1: // Contextual Tips
75 m.cfg.HideTips = !m.cfg.HideTips
76 _ = config.SaveConfig(m.cfg)
77 saved = true
78 case 2: // Desktop Notifications
79 m.cfg.DisableNotifications = !m.cfg.DisableNotifications
80 _ = config.SaveConfig(m.cfg)
81 saved = true
82 case 3: // Split Pane View
83 m.cfg.EnableSplitPane = !m.cfg.EnableSplitPane
84 _ = config.SaveConfig(m.cfg)
85 saved = true
86 case 4: // Threaded Conversation View
87 m.cfg.EnableThreaded = !m.cfg.EnableThreaded
88 _ = config.SaveConfig(m.cfg)
89 saved = true
90 case 5: // Date Format
91 switch m.cfg.DateFormat {
92 case config.DateFormatEU:
93 m.cfg.DateFormat = config.DateFormatUS
94 case config.DateFormatUS:
95 m.cfg.DateFormat = config.DateFormatISO
96 default: // or ISO
97 m.cfg.DateFormat = config.DateFormatEU
98 }
99 _ = config.SaveConfig(m.cfg)
100 saved = true
101 case 6: // Language
102 // Cycle through available languages
103 langs := i18n.LanguageCodes()
104 currentLang := m.cfg.GetLanguage()
105 currentIdx := -1
106 for i, lang := range langs {
107 if lang == currentLang {
108 currentIdx = i
109 break
110 }
111 }
112 nextIdx := (currentIdx + 1) % len(langs)
113 m.cfg.Language = langs[nextIdx]
114 _ = config.SaveConfig(m.cfg)
115 // Apply language change immediately
116 i18n.GetManager().SetLanguage(m.cfg.Language)
117 // Trigger full UI rebuild
118 return m, tea.Batch(
119 func() tea.Msg { return ConfigSavedMsg{} },
120 func() tea.Msg { return LanguageChangedMsg{} },
121 )
122 case 7: // Edit Signature
123 if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" {
124 return m, func() tea.Msg { return GoToSignatureEditorMsg{} }
125 }
126 }
127 if saved {
128 return m, func() tea.Msg { return ConfigSavedMsg{} }
129 }
130 }
131 }
132 return m, nil
133}
134
135func (m *Settings) viewGeneral() string {
136 var b strings.Builder
137
138 b.WriteString(titleStyle.Render("General Settings") + "\n\n")
139
140 options := m.buildGeneralOptions()
141
142 for i, opt := range options {
143 cursor := " "
144 style := accountItemStyle
145 if m.generalCursor == i {
146 cursor = "> "
147 style = selectedAccountItemStyle
148 }
149
150 label := opt.labelKey
151 if !opt.isAccountSig {
152 label = t(opt.labelKey)
153 }
154 text := fmt.Sprintf("%s: %s", label, opt.value)
155 if opt.labelKey == "settings_general.signature" || opt.isAccountSig {
156 text = fmt.Sprintf("%s (%s)", label, opt.value)
157 }
158
159 b.WriteString(style.Render(cursor+text) + "\n")
160 }
161
162 b.WriteString("\n\n")
163
164 if !m.cfg.HideTips && m.generalCursor < len(options) {
165 b.WriteString(TipStyle.Render("Tip: " + options[m.generalCursor].tip))
166 }
167
168 return b.String()
169}
170
171func onOff(b bool) string {
172 if b {
173 return t("settings_general.on")
174 }
175 return t("settings_general.off")
176}
177
178func getDateFormatLabel(f string) string {
179 if f == "" {
180 f = config.DateFormatEU
181 }
182 switch f {
183 case config.DateFormatUS:
184 return "US (MM/DD/YYYY hh:MM AM)"
185 case config.DateFormatISO:
186 return "ISO (YYYY-MM-DD HH:MM)"
187 default:
188 return "EU (DD/MM/YYYY HH:MM)"
189 }
190}
191
192func getSignatureStatus() string {
193 if config.HasSignature() {
194 return t("settings_general.signature_configured")
195 }
196 return t("settings_general.signature_not_configured")
197}
198
199func getLanguageLabel(langCode string) string {
200 if locale, ok := i18n.GetLanguage(langCode); ok {
201 return fmt.Sprintf("%s (%s)", locale.NativeName, locale.Code)
202 }
203 return langCode
204}