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