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
 18// item now holds its original index from the main email slice.
 19type item struct {
 20	title, desc   string
 21	originalIndex int
 22}
 23
 24func (i item) Title() string       { return i.title }
 25func (i item) Description() string { return i.desc }
 26func (i item) FilterValue() string { return i.title + " " + i.desc }
 27
 28type itemDelegate struct{}
 29
 30func (d itemDelegate) Height() int                             { return 1 }
 31func (d itemDelegate) Spacing() int                            { return 0 }
 32func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
 33func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
 34	i, ok := listItem.(item)
 35	if !ok {
 36		return
 37	}
 38
 39	str := fmt.Sprintf("%d. %s", index+1, i.title)
 40
 41	fn := itemStyle.Render
 42	if index == m.Index() {
 43		fn = func(s ...string) string {
 44			return selectedItemStyle.Render("> " + s[0])
 45		}
 46	}
 47
 48	fmt.Fprint(w, fn(str))
 49}
 50
 51// Inbox is now stateful to handle pagination.
 52type Inbox struct {
 53	list        list.Model
 54	isFetching  bool
 55	emailsCount int
 56}
 57
 58func NewInbox(emails []fetcher.Email) *Inbox {
 59	items := make([]list.Item, len(emails))
 60	for i, email := range emails {
 61		items[i] = item{
 62			title:         email.Subject,
 63			desc:          email.From,
 64			originalIndex: i, // Store the original index here.
 65		}
 66	}
 67
 68	l := list.New(items, itemDelegate{}, 20, 14)
 69	l.Title = "Inbox"
 70	l.SetShowStatusBar(true)
 71	l.SetFilteringEnabled(true)
 72	l.Styles.Title = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
 73	l.Styles.PaginationStyle = paginationStyle
 74	l.Styles.HelpStyle = inboxHelpStyle
 75	l.SetStatusBarItemName("email", "emails")
 76
 77	return &Inbox{
 78		list:        l,
 79		isFetching:  false,
 80		emailsCount: len(emails),
 81	}
 82}
 83
 84func (m *Inbox) Init() tea.Cmd {
 85	return nil
 86}
 87
 88func (m *Inbox) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 89	var cmds []tea.Cmd
 90
 91	switch msg := msg.(type) {
 92	case tea.KeyMsg:
 93		// When the user presses enter, we look at the selected item and send
 94		// a message with its *original* index.
 95		if msg.String() == "enter" {
 96			selectedItem, ok := m.list.SelectedItem().(item)
 97			if ok {
 98				return m, func() tea.Msg {
 99					// Use the stored original index, which is correct even when filtered.
100					return ViewEmailMsg{Index: selectedItem.originalIndex}
101				}
102			}
103		}
104	case tea.WindowSizeMsg:
105		m.list.SetWidth(msg.Width)
106		return m, nil
107
108	case FetchingMoreEmailsMsg:
109		m.isFetching = true
110		m.list.Title = "Fetching more emails..."
111		return m, nil
112
113	case EmailsAppendedMsg:
114		m.isFetching = false
115		m.list.Title = "Inbox"
116		newItems := make([]list.Item, len(msg.Emails))
117		for i, email := range msg.Emails {
118			newItems[i] = item{
119				title:         email.Subject,
120				desc:          email.From,
121				originalIndex: m.emailsCount + i, // The original index continues to grow.
122			}
123		}
124		currentItems := m.list.Items()
125		allItems := append(currentItems, newItems...)
126		cmd := m.list.SetItems(allItems)
127		m.emailsCount += len(msg.Emails)
128		cmds = append(cmds, cmd)
129		return m, tea.Batch(cmds...)
130	}
131
132	// Infinite scroll logic
133	if !m.isFetching && len(m.list.Items()) > 0 && m.list.Index() >= len(m.list.Items())-5 {
134		cmds = append(cmds, func() tea.Msg {
135			return FetchMoreEmailsMsg{Offset: uint32(m.emailsCount)}
136		})
137	}
138
139	// New logic to fetch more emails when filtering is active
140	if m.list.FilterState() == list.Filtering && !m.isFetching {
141		m.isFetching = true
142		m.list.Title = "Fetching more emails..."
143		cmds = append(cmds, func() tea.Msg {
144			return FetchMoreEmailsMsg{Offset: uint32(m.emailsCount)}
145		})
146	}
147
148	var cmd tea.Cmd
149	var newModel list.Model
150	newModel, cmd = m.list.Update(msg)
151	m.list = newModel
152	cmds = append(cmds, cmd)
153	return m, tea.Batch(cmds...)
154}
155
156func (m *Inbox) View() string {
157	return "\n" + m.list.View()
158}