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 := []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}