inbox.go

  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}