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