settings_general.go

  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}