email_view.go

  1package tui
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/andrinoff/email-cli/fetcher"
  8	"github.com/andrinoff/email-cli/view"
  9	"github.com/charmbracelet/bubbles/viewport"
 10	tea "github.com/charmbracelet/bubbletea"
 11	"github.com/charmbracelet/lipgloss"
 12)
 13
 14var (
 15	emailHeaderStyle   = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).Padding(0, 1)
 16	attachmentBoxStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, false, false, true).PaddingLeft(2).MarginTop(1)
 17)
 18
 19type EmailView struct {
 20	viewport           viewport.Model
 21	email              fetcher.Email
 22	attachmentCursor   int
 23	focusOnAttachments bool
 24}
 25
 26func NewEmailView(email fetcher.Email, width, height int) *EmailView {
 27	body, err := view.ProcessBody(email.Body)
 28	if err != nil {
 29		body = fmt.Sprintf("Error rendering body: %v", err)
 30	}
 31
 32	header := fmt.Sprintf("From: %s\nSubject: %s", email.From, email.Subject)
 33	headerHeight := lipgloss.Height(header) + 2
 34
 35	// Calculate height for attachments if they exist
 36	attachmentHeight := 0
 37	if len(email.Attachments) > 0 {
 38		attachmentHeight = len(email.Attachments) + 2 // +2 for title and border
 39	}
 40
 41	vp := viewport.New(width, height-headerHeight-attachmentHeight)
 42	vp.SetContent(body)
 43
 44	return &EmailView{
 45		viewport: vp,
 46		email:    email,
 47	}
 48}
 49
 50func (m *EmailView) Init() tea.Cmd {
 51	return nil
 52}
 53
 54func (m *EmailView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 55	var cmd tea.Cmd
 56	var cmds []tea.Cmd
 57
 58	switch msg := msg.(type) {
 59	case tea.KeyMsg:
 60		// Handle 'esc' key locally
 61		if msg.Type == tea.KeyEsc {
 62			if m.focusOnAttachments {
 63				m.focusOnAttachments = false
 64				return m, nil
 65			}
 66			return m, func() tea.Msg { return BackToInboxMsg{} }
 67		}
 68
 69		if m.focusOnAttachments {
 70			switch msg.String() {
 71			case "up", "k":
 72				if m.attachmentCursor > 0 {
 73					m.attachmentCursor--
 74				}
 75			case "down", "j":
 76				if m.attachmentCursor < len(m.email.Attachments)-1 {
 77					m.attachmentCursor++
 78				}
 79			case "enter":
 80				if len(m.email.Attachments) > 0 {
 81					selected := m.email.Attachments[m.attachmentCursor]
 82					return m, func() tea.Msg {
 83						return DownloadAttachmentMsg{Filename: selected.Filename, Data: selected.Data}
 84					}
 85				}
 86			case "tab":
 87				m.focusOnAttachments = false
 88			}
 89		} else {
 90			switch msg.String() {
 91			case "r":
 92				return m, func() tea.Msg { return ReplyToEmailMsg{Email: m.email} }
 93			case "tab":
 94				if len(m.email.Attachments) > 0 {
 95					m.focusOnAttachments = true
 96				}
 97			}
 98		}
 99	case tea.WindowSizeMsg:
100		header := fmt.Sprintf("From: %s\nSubject: %s", m.email.From, m.email.Subject)
101		headerHeight := lipgloss.Height(header) + 2
102		attachmentHeight := 0
103		if len(m.email.Attachments) > 0 {
104			attachmentHeight = len(m.email.Attachments) + 2
105		}
106		m.viewport.Width = msg.Width
107		m.viewport.Height = msg.Height - headerHeight - attachmentHeight
108	}
109
110	m.viewport, cmd = m.viewport.Update(msg)
111	cmds = append(cmds, cmd)
112
113	return m, tea.Batch(cmds...)
114}
115
116func (m *EmailView) View() string {
117	header := fmt.Sprintf("From: %s\nSubject: %s", m.email.From, m.email.Subject)
118	styledHeader := emailHeaderStyle.Width(m.viewport.Width).Render(header)
119
120	var help string
121	if m.focusOnAttachments {
122		help = helpStyle.Render("↑/↓: navigate • enter: download • esc/tab: back to email body")
123	} else {
124		help = helpStyle.Render("r: reply • tab: focus attachments • esc: back to inbox")
125	}
126
127	var attachmentView string
128	if len(m.email.Attachments) > 0 {
129		var b strings.Builder
130		b.WriteString("Attachments:\n")
131		for i, attachment := range m.email.Attachments {
132			cursor := "  "
133			style := itemStyle
134			if m.focusOnAttachments && i == m.attachmentCursor {
135				cursor = "> "
136				style = selectedItemStyle
137			}
138			b.WriteString(style.Render(fmt.Sprintf("%s%s", cursor, attachment.Filename)))
139			b.WriteString("\n")
140		}
141		attachmentView = attachmentBoxStyle.Render(b.String())
142	}
143
144	return fmt.Sprintf("%s\n%s\n%s\n%s", styledHeader, m.viewport.View(), attachmentView, help)
145}