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}