composer.go

  1package tui
  2
  3import (
  4	"fmt"
  5	"strings"
  6	"time"
  7
  8	"github.com/andrinoff/email-cli/config"
  9	"github.com/andrinoff/email-cli/sender"
 10	"github.com/charmbracelet/bubbles/textarea"
 11	"github.com/charmbracelet/bubbles/textinput"
 12	tea "github.com/charmbracelet/bubbletea"
 13)
 14
 15type composerModel struct {
 16	recipientInput textinput.Model
 17	subjectInput   textinput.Model
 18	bodyArea       textarea.Model
 19	index          int
 20	sending        bool
 21	sent           bool
 22	err            error
 23	config         *config.Config
 24}
 25
 26func initialComposerModel(cfg *config.Config) composerModel {
 27	ti := textinput.New()
 28	ti.Placeholder = "recipient@example.com"
 29	ti.Focus()
 30	ti.CharLimit = 156
 31	ti.Width = 50
 32	ti.Prompt = "To:      "
 33	si := textinput.New()
 34	si.Placeholder = "Hello there!"
 35	si.CharLimit = 156
 36	si.Width = 50
 37	si.Prompt = "Subject: "
 38	ta := textarea.New()
 39	ta.Placeholder = "Dear friend, ..."
 40	ta.SetWidth(50)
 41	ta.SetHeight(10)
 42	return composerModel{
 43		recipientInput: ti,
 44		subjectInput:   si,
 45		bodyArea:       ta,
 46		index:          0,
 47		config:         cfg,
 48		err:            nil,
 49	}
 50}
 51
 52func (m composerModel) Init() tea.Cmd { return textinput.Blink }
 53
 54func (m composerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 55	var cmds []tea.Cmd
 56	switch msg := msg.(type) {
 57	case tea.KeyMsg:
 58		switch msg.Type {
 59		case tea.KeyCtrlC, tea.KeyEsc:
 60			return m, tea.Quit
 61		case tea.KeyCtrlS:
 62			if !m.sending {
 63				m.sending = true
 64				to := []string{m.recipientInput.Value()}
 65				subject := m.subjectInput.Value()
 66				body := m.bodyArea.Value()
 67				return m, func() tea.Msg {
 68					err := sender.SendEmail(m.config, to, subject, body)
 69					if err != nil {
 70						return err
 71					}
 72					return "sent"
 73				}
 74			}
 75		case tea.KeyTab, tea.KeyShiftTab:
 76			if msg.Type == tea.KeyTab {
 77				m.index = (m.index + 1) % 3
 78			} else {
 79				m.index--
 80				if m.index < 0 {
 81					m.index = 2
 82				}
 83			}
 84			m.recipientInput.Blur()
 85			m.subjectInput.Blur()
 86			m.bodyArea.Blur()
 87			switch m.index {
 88			case 0:
 89				m.recipientInput.Focus()
 90			case 1:
 91				m.subjectInput.Focus()
 92			case 2:
 93				m.bodyArea.Focus()
 94			}
 95			return m, tea.Batch(cmds...)
 96		}
 97	case string:
 98		if msg == "sent" {
 99			m.sending = false
100			m.sent = true
101			return m, tea.Sequence(func() tea.Msg { time.Sleep(time.Second); return nil }, tea.Quit)
102		}
103	case error:
104		m.sending = false
105		m.err = msg
106		return m, nil
107	}
108	var cmd tea.Cmd
109	switch m.index {
110	case 0:
111		m.recipientInput, cmd = m.recipientInput.Update(msg)
112	case 1:
113		m.subjectInput, cmd = m.subjectInput.Update(msg)
114	case 2:
115		m.bodyArea, cmd = m.bodyArea.Update(msg)
116	}
117	cmds = append(cmds, cmd)
118	return m, tea.Batch(cmds...)
119}
120
121func (m composerModel) View() string {
122	if m.err != nil {
123		return InfoStyle.Render(fmt.Sprintf("\n   Error: %v \n\n Press any key to exit.", m.err))
124	}
125	if m.sent {
126		return SuccessStyle.Render("\n   ✓ Email sent successfully! \n\n")
127	}
128	if m.sending {
129		return InfoStyle.Render("\n   Sending email... \n\n")
130	}
131	var s strings.Builder
132	s.WriteString("\n")
133	s.WriteString(m.recipientInput.View() + "\n")
134	s.WriteString(m.subjectInput.View() + "\n")
135	s.WriteString(m.bodyArea.View())
136	help := fmt.Sprintf("\n\n %s | %s | %s \n", "Tab: Next Field", "Esc: Quit", "Ctrl+S: Send")
137	s.WriteString(HelpStyle.Render(help))
138	return DialogBoxStyle.Render(s.String())
139}
140
141// RunComposer starts the email composer UI.
142func RunComposer(cfg *config.Config) error {
143	p := tea.NewProgram(initialComposerModel(cfg))
144	_, err := p.Run()
145	return err
146}