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