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