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