diff --git a/tui/inbox.go b/tui/inbox.go index 4f1fbc872fab70f3f6e539c64d3b26ebb3e5fdb9..166011a9b34c79d2adf0bfca8bd3a36593dd06b8 100644 --- a/tui/inbox.go +++ b/tui/inbox.go @@ -159,6 +159,8 @@ func (m *Inbox) updateList() { showAccountLabel = false } + m.emailsCount = len(displayEmails) + items := make([]list.Item, len(displayEmails)) for i, email := range displayEmails { accountEmail := "" @@ -211,21 +213,20 @@ func (m *Inbox) updateList() { } m.list = l - m.emailsCount = len(displayEmails) } func (m *Inbox) getTitle() string { var title string if m.currentAccountID == "" { - title = m.getBaseTitle() + " - All Accounts" + title = m.getBaseTitleWithCount() + " - All Accounts" } else { - title = m.getBaseTitle() + title = m.getBaseTitleWithCount() for _, acc := range m.accounts { if acc.ID == m.currentAccountID { if acc.Name != "" { - title = fmt.Sprintf("%s - %s", m.getBaseTitle(), acc.Name) + title = fmt.Sprintf("%s - %s", m.getBaseTitleWithCount(), acc.Name) } else { - title = fmt.Sprintf("%s - %s", m.getBaseTitle(), acc.Email) + title = fmt.Sprintf("%s - %s", m.getBaseTitleWithCount(), acc.Email) } break } @@ -234,6 +235,9 @@ func (m *Inbox) getTitle() string { if m.isRefreshing { title += " (refreshing...)" } + if m.isFetching { + title += " (loading more...)" + } return title } @@ -246,6 +250,10 @@ func (m *Inbox) getBaseTitle() string { } } +func (m *Inbox) getBaseTitleWithCount() string { + return fmt.Sprintf("%s (%d)", m.getBaseTitle(), m.emailsCount) +} + func (m *Inbox) Init() tea.Cmd { return nil } @@ -312,7 +320,7 @@ func (m *Inbox) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case FetchingMoreEmailsMsg: m.isFetching = true - m.list.Title = "Fetching more emails..." + m.list.Title = m.getTitle() return m, nil case EmailsAppendedMsg: @@ -376,25 +384,56 @@ func (m *Inbox) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - if !m.isFetching && len(m.list.Items()) > 0 && m.list.Index() >= len(m.list.Items())-5 { - accountID := m.currentAccountID - offset := uint32(m.emailsCount) - if accountID == "" && len(m.accounts) > 0 { - // For "ALL" view, we'd need to fetch from all accounts - // For simplicity, don't auto-fetch more in ALL view - } else if accountID != "" { - cmds = append(cmds, func() tea.Msg { - return FetchMoreEmailsMsg{Offset: offset, AccountID: accountID, Mailbox: m.mailbox} - }) - } - } - var cmd tea.Cmd m.list, cmd = m.list.Update(msg) cmds = append(cmds, cmd) + + if m.shouldFetchMore() { + cmds = append(cmds, m.fetchMoreCmds()...) + } return m, tea.Batch(cmds...) } +func (m *Inbox) shouldFetchMore() bool { + if m.isFetching { + return false + } + if len(m.list.Items()) == 0 { + return false + } + if m.list.FilterState() == list.Filtering { + return false + } + return m.list.Index() >= len(m.list.Items())-1 +} + +func (m *Inbox) fetchMoreCmds() []tea.Cmd { + var cmds []tea.Cmd + if m.currentAccountID == "" { + if len(m.accounts) == 0 { + return nil + } + for _, acc := range m.accounts { + accountID := acc.ID + offset := uint32(len(m.emailsByAccount[accountID])) + cmds = append(cmds, func(id string, off uint32) tea.Cmd { + return func() tea.Msg { + return FetchMoreEmailsMsg{Offset: off, AccountID: id, Mailbox: m.mailbox} + } + }(accountID, offset)) + } + return cmds + } + + offset := uint32(len(m.emailsByAccount[m.currentAccountID])) + cmds = append(cmds, func(id string, off uint32) tea.Cmd { + return func() tea.Msg { + return FetchMoreEmailsMsg{Offset: off, AccountID: id, Mailbox: m.mailbox} + } + }(m.currentAccountID, offset)) + return cmds +} + func (m *Inbox) View() string { var b strings.Builder diff --git a/tui/inbox_test.go b/tui/inbox_test.go index 765d75e9940a61c5421ab6f733dccc29405692e7..ce18d032dadc5a1ada31d734ea1e9cb8c1103757 100644 --- a/tui/inbox_test.go +++ b/tui/inbox_test.go @@ -1,6 +1,7 @@ package tui import ( + "strings" "testing" "time" @@ -9,6 +10,28 @@ import ( "github.com/floatpane/matcha/fetcher" ) +func collectMsgs(cmd tea.Cmd) []tea.Msg { + if cmd == nil { + return nil + } + msg := cmd() + switch batch := msg.(type) { + case tea.BatchMsg: + var msgs []tea.Msg + for _, m := range batch { + if m != nil { + msgs = append(msgs, m) + } + } + return msgs + default: + if msg != nil { + return []tea.Msg{msg} + } + } + return nil +} + // TestInboxUpdate verifies the state transitions in the inbox view. func TestInboxUpdate(t *testing.T) { // Create sample accounts @@ -248,3 +271,76 @@ func TestInboxGetEmailAtIndex(t *testing.T) { t.Error("Expected nil for negative index, got non-nil") } } + +func TestInboxTitleShowsCount(t *testing.T) { + accounts := []config.Account{ + {ID: "account-1", Email: "test@example.com"}, + } + + emails := []fetcher.Email{ + {UID: 1, From: "sender@example.com", Subject: "Email 1", AccountID: "account-1", Date: time.Now()}, + {UID: 2, From: "sender@example.com", Subject: "Email 2", AccountID: "account-1", Date: time.Now().Add(-time.Minute)}, + {UID: 3, From: "sender@example.com", Subject: "Email 3", AccountID: "account-1", Date: time.Now().Add(-2 * time.Minute)}, + } + + inbox := NewInbox(emails, accounts) + + if !strings.Contains(inbox.list.Title, "Inbox (3)") { + t.Fatalf("expected inbox title to contain count, got %q", inbox.list.Title) + } +} + +func TestSentInboxTitleShowsCount(t *testing.T) { + accounts := []config.Account{ + {ID: "account-1", Email: "test@example.com"}, + } + + emails := []fetcher.Email{ + {UID: 1, From: "sender@example.com", Subject: "Email 1", AccountID: "account-1", Date: time.Now()}, + {UID: 2, From: "sender@example.com", Subject: "Email 2", AccountID: "account-1", Date: time.Now().Add(-time.Minute)}, + } + + inbox := NewSentInbox(emails, accounts) + + if !strings.Contains(inbox.list.Title, "Sent (2)") { + t.Fatalf("expected sent title to contain count, got %q", inbox.list.Title) + } +} + +func TestFetchMoreTriggeredAtListEnd(t *testing.T) { + accounts := []config.Account{ + {ID: "account-1", Email: "test@example.com"}, + } + + emails := []fetcher.Email{ + {UID: 1, From: "a@example.com", Subject: "Email 1", AccountID: "account-1", Date: time.Now()}, + {UID: 2, From: "b@example.com", Subject: "Email 2", AccountID: "account-1", Date: time.Now().Add(-time.Minute)}, + } + + inbox := NewInbox(emails, accounts) + + _, cmd := inbox.Update(tea.KeyMsg{Type: tea.KeyDown}) + msgs := collectMsgs(cmd) + + var fetchMsg FetchMoreEmailsMsg + for _, m := range msgs { + if msg, ok := m.(FetchMoreEmailsMsg); ok { + fetchMsg = msg + break + } + } + + if fetchMsg.AccountID == "" { + t.Fatal("expected a FetchMoreEmailsMsg when reaching end of the list") + } + + if fetchMsg.Offset != uint32(len(emails)) { + t.Fatalf("expected offset %d, got %d", len(emails), fetchMsg.Offset) + } + if fetchMsg.AccountID != "account-1" { + t.Fatalf("expected account ID 'account-1', got %q", fetchMsg.AccountID) + } + if fetchMsg.Mailbox != MailboxInbox { + t.Fatalf("expected MailboxInbox, got %s", fetchMsg.Mailbox) + } +}