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{"\ueb1c Inbox", "\ueb1b Compose Email", "\uf1d8 Sent"}
44 if hasSavedDrafts {
45 choices = append(choices, "\uec0e Drafts")
46 }
47 choices = append(choices, "\uf013 Settings", "\uea81 Trash & Archive")
48 return Choice{
49 choices: choices,
50 hasSavedDrafts: hasSavedDrafts,
51 UpdateAvailable: false,
52 LatestVersion: "",
53 CurrentVersion: "",
54 }
55
56}
57
58func (m Choice) Init() tea.Cmd {
59 return nil
60}
61
62func (m Choice) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
63 switch msg := msg.(type) {
64 case tea.KeyMsg:
65 switch msg.String() {
66 case "up", "k":
67 if m.cursor > 0 {
68 m.cursor--
69 }
70 case "down", "j":
71 if m.cursor < len(m.choices)-1 {
72 m.cursor++
73 }
74 case "enter":
75 selectedChoice := m.choices[m.cursor]
76 switch selectedChoice {
77 case "\ueb1c Inbox":
78 return m, func() tea.Msg { return GoToInboxMsg{} }
79 case "\ueb1b Compose Email":
80 return m, func() tea.Msg { return GoToSendMsg{} }
81 case "\uf1d8 Sent":
82 return m, func() tea.Msg { return GoToSentInboxMsg{} }
83 case "\uec0e Drafts":
84 return m, func() tea.Msg { return GoToDraftsMsg{} }
85 case "\uf013 Settings":
86 return m, func() tea.Msg { return GoToSettingsMsg{} }
87 case "\uea81 Trash & Archive":
88 return m, func() tea.Msg { return GoToTrashArchiveMsg{} }
89 }
90
91 }
92 }
93
94 // Handle update notification from other package without importing its type directly.
95 // We look for a struct named 'UpdateAvailableMsg' that contains 'Latest' and 'Current' string fields.
96 rv := reflect.ValueOf(msg)
97 if rv.IsValid() && rv.Kind() == reflect.Struct && rv.Type().Name() == "UpdateAvailableMsg" {
98 f := rv.FieldByName("Latest")
99 c := rv.FieldByName("Current")
100 updated := false
101 if f.IsValid() && f.Kind() == reflect.String {
102 m.LatestVersion = f.String()
103 updated = true
104 }
105 if c.IsValid() && c.Kind() == reflect.String {
106 m.CurrentVersion = c.String()
107 updated = true
108 }
109 if updated {
110 m.UpdateAvailable = true
111 return m, nil
112 }
113 }
114
115 return m, nil
116}
117
118func (m Choice) View() string {
119 var b strings.Builder
120
121 b.WriteString(logoStyle.Render(choiceLogo))
122 b.WriteString("\n")
123 b.WriteString(listHeader.Render("What would you like to do?"))
124 b.WriteString("\n\n")
125
126 // If we detected an update, show a short message under the header.
127 if m.UpdateAvailable {
128 updateStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Padding(0, 1)
129 cur := m.CurrentVersion
130 if cur == "" {
131 cur = "unknown"
132 }
133 msg := fmt.Sprintf("Update available: %s (installed: %s) — run `matcha update` to upgrade", m.LatestVersion, cur)
134 b.WriteString(updateStyle.Render(msg))
135 b.WriteString("\n\n")
136 }
137
138 for i, choice := range m.choices {
139 if m.cursor == i {
140 b.WriteString(selectedItemStyle.Render(fmt.Sprintf("> %s", choice)))
141 } else {
142 b.WriteString(itemStyle.Render(fmt.Sprintf(" %s", choice)))
143 }
144 b.WriteString("\n")
145 }
146
147 b.WriteString("\n\n")
148 b.WriteString(helpStyle.Render("Use ↑/↓ to navigate, enter to select, and ctrl+c to quit."))
149
150 return docStyle.Render(b.String())
151}