choice.go

  1package tui
  2
  3import (
  4	"fmt"
  5	"reflect"
  6	"strings"
  7
  8	tea "github.com/charmbracelet/bubbletea"
  9	"github.com/charmbracelet/lipgloss"
 10	"github.com/floatpane/matcha/config"
 11)
 12
 13// Styles defined locally to avoid import issues.
 14var (
 15	docStyle          = lipgloss.NewStyle().Margin(1, 2)
 16	titleStyle        = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFDF5")).Background(lipgloss.Color("#25A065")).Padding(0, 1)
 17	logoStyle         = lipgloss.NewStyle().Foreground(lipgloss.Color("42"))
 18	listHeader        = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).PaddingBottom(1)
 19	itemStyle         = lipgloss.NewStyle().PaddingLeft(2)
 20	selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("42"))
 21)
 22
 23// ASCII logo for the start screen
 24const choiceLogo = `
 25                    __       __
 26   ____ ___  ____ _/ /______/ /_  ____ _
 27  / __ '__ \/ __ '/ __/ ___/ __ \/ __ '/
 28 / / / / / / /_/ / /_/ /__/ / / / /_/ /
 29/_/ /_/ /_/\__,_/\__/\___/_/ /_/\__,_/
 30`
 31
 32type Choice struct {
 33	cursor          int
 34	choices         []string
 35	hasSavedDrafts  bool
 36	UpdateAvailable bool
 37	LatestVersion   string
 38	CurrentVersion  string
 39}
 40
 41func NewChoice() Choice {
 42	hasSavedDrafts := config.HasDrafts()
 43	choices := []string{"View Inbox", "View Sent", "Compose Email"}
 44	if hasSavedDrafts {
 45		choices = append(choices, "Drafts")
 46	}
 47	choices = append(choices, "Settings")
 48	return Choice{
 49		choices:         choices,
 50		hasSavedDrafts:  hasSavedDrafts,
 51		UpdateAvailable: false,
 52		LatestVersion:   "",
 53		CurrentVersion:  "",
 54	}
 55}
 56
 57func (m Choice) Init() tea.Cmd {
 58	return nil
 59}
 60
 61func (m Choice) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 62	switch msg := msg.(type) {
 63	case tea.KeyMsg:
 64		switch msg.String() {
 65		case "up", "k":
 66			if m.cursor > 0 {
 67				m.cursor--
 68			}
 69		case "down", "j":
 70			if m.cursor < len(m.choices)-1 {
 71				m.cursor++
 72			}
 73		case "enter":
 74			selectedChoice := m.choices[m.cursor]
 75			switch selectedChoice {
 76			case "View Inbox":
 77				return m, func() tea.Msg { return GoToInboxMsg{} }
 78			case "View Sent":
 79				return m, func() tea.Msg { return GoToSentInboxMsg{} }
 80			case "Compose Email":
 81				return m, func() tea.Msg { return GoToSendMsg{} }
 82			case "Drafts":
 83				return m, func() tea.Msg { return GoToDraftsMsg{} }
 84			case "Settings":
 85				return m, func() tea.Msg { return GoToSettingsMsg{} }
 86			}
 87		}
 88	}
 89
 90	// Handle update notification from other package without importing its type directly.
 91	// We look for a struct named 'UpdateAvailableMsg' that contains 'Latest' and 'Current' string fields.
 92	rv := reflect.ValueOf(msg)
 93	if rv.IsValid() && rv.Kind() == reflect.Struct && rv.Type().Name() == "UpdateAvailableMsg" {
 94		f := rv.FieldByName("Latest")
 95		c := rv.FieldByName("Current")
 96		updated := false
 97		if f.IsValid() && f.Kind() == reflect.String {
 98			m.LatestVersion = f.String()
 99			updated = true
100		}
101		if c.IsValid() && c.Kind() == reflect.String {
102			m.CurrentVersion = c.String()
103			updated = true
104		}
105		if updated {
106			m.UpdateAvailable = true
107			return m, nil
108		}
109	}
110
111	return m, nil
112}
113
114func (m Choice) View() string {
115	var b strings.Builder
116
117	b.WriteString(logoStyle.Render(choiceLogo))
118	b.WriteString("\n")
119	b.WriteString(listHeader.Render("What would you like to do?"))
120	b.WriteString("\n\n")
121
122	// If we detected an update, show a short message under the header.
123	if m.UpdateAvailable {
124		updateStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Padding(0, 1)
125		cur := m.CurrentVersion
126		if cur == "" {
127			cur = "unknown"
128		}
129		msg := fmt.Sprintf("Update available: %s (installed: %s) — run `matcha update` to upgrade", m.LatestVersion, cur)
130		b.WriteString(updateStyle.Render(msg))
131		b.WriteString("\n\n")
132	}
133
134	for i, choice := range m.choices {
135		if m.cursor == i {
136			b.WriteString(selectedItemStyle.Render(fmt.Sprintf("> %s", choice)))
137		} else {
138			b.WriteString(itemStyle.Render(fmt.Sprintf("  %s", choice)))
139		}
140		b.WriteString("\n")
141	}
142
143	b.WriteString("\n\n")
144	b.WriteString(helpStyle.Render("Use ↑/↓ to navigate, enter to select, and ctrl+c to quit."))
145
146	return docStyle.Render(b.String())
147}