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