1package tui
2
3import (
4 "fmt"
5 "io"
6
7 "github.com/andrinoff/email-cli/fetcher"
8 "github.com/charmbracelet/bubbles/list"
9 tea "github.com/charmbracelet/bubbletea"
10 "github.com/charmbracelet/lipgloss"
11)
12
13var (
14 paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
15 inboxHelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
16)
17
18type item struct {
19 title, desc string
20}
21
22func (i item) Title() string { return i.title }
23func (i item) Description() string { return i.desc }
24func (i item) FilterValue() string { return i.title + " " + i.desc }
25
26type itemDelegate struct{}
27
28func (d itemDelegate) Height() int { return 1 }
29func (d itemDelegate) Spacing() int { return 0 }
30func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
31func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
32 i, ok := listItem.(item)
33 if !ok {
34 return
35 }
36
37 str := fmt.Sprintf("%d. %s", index+1, i.title)
38
39 fn := itemStyle.Render
40 if index == m.Index() {
41 fn = func(s ...string) string {
42 return selectedItemStyle.Render("> " + s[0])
43 }
44 }
45
46 fmt.Fprint(w, fn(str))
47}
48
49// Inbox is now stateful to handle pagination.
50type Inbox struct {
51 list list.Model
52 isFetching bool
53 emailsCount int
54}
55
56func NewInbox(emails []fetcher.Email) *Inbox {
57 items := make([]list.Item, len(emails))
58 for i, email := range emails {
59 items[i] = item{
60 title: email.Subject,
61 desc: email.From,
62 }
63 }
64
65 l := list.New(items, itemDelegate{}, 20, 14)
66 l.Title = "Inbox"
67 l.SetShowStatusBar(true)
68 l.SetFilteringEnabled(true)
69 l.Styles.Title = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
70 l.Styles.PaginationStyle = paginationStyle
71 l.Styles.HelpStyle = inboxHelpStyle
72 l.SetStatusBarItemName("email", "emails")
73
74 return &Inbox{
75 list: l,
76 isFetching: false,
77 emailsCount: len(emails),
78 }
79}
80
81func (m *Inbox) Init() tea.Cmd {
82 return nil
83}
84
85func (m *Inbox) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
86 var cmds []tea.Cmd
87
88 switch msg := msg.(type) {
89 case tea.KeyMsg:
90 if msg.String() == "enter" {
91 i, ok := m.list.SelectedItem().(item)
92 if ok {
93 _ = i
94 return m, func() tea.Msg {
95 return ViewEmailMsg{Index: m.list.Index()}
96 }
97 }
98 }
99 case tea.WindowSizeMsg:
100 m.list.SetWidth(msg.Width)
101 return m, nil
102
103 case FetchingMoreEmailsMsg:
104 m.isFetching = true
105 m.list.Title = "Fetching more emails..."
106 return m, nil
107
108 case EmailsAppendedMsg:
109 m.isFetching = false
110 m.list.Title = "Inbox"
111 newItems := make([]list.Item, len(msg.Emails))
112 for i, email := range msg.Emails {
113 newItems[i] = item{
114 title: email.Subject,
115 desc: email.From,
116 }
117 }
118 // Correctly append new items to the list.
119 currentItems := m.list.Items()
120 allItems := append(currentItems, newItems...)
121 cmd := m.list.SetItems(allItems)
122 m.emailsCount += len(msg.Emails)
123 cmds = append(cmds, cmd)
124 return m, tea.Batch(cmds...)
125 }
126
127 // Infinite scroll logic
128 if !m.isFetching && m.list.Index() >= len(m.list.Items())-5 {
129 cmds = append(cmds, func() tea.Msg {
130 return FetchMoreEmailsMsg{Offset: uint32(m.emailsCount)}
131 })
132 }
133
134 var cmd tea.Cmd
135 var newModel list.Model
136 newModel, cmd = m.list.Update(msg)
137 m.list = newModel
138 cmds = append(cmds, cmd)
139 return m, tea.Batch(cmds...)
140}
141
142func (m *Inbox) View() string {
143 return "\n" + m.list.View()
144}