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