main.go

  1package main
  2
  3import (
  4	"fmt"
  5	"log"
  6	"os"
  7	"time"
  8
  9	"github.com/andrinoff/email-cli/config"
 10	"github.com/andrinoff/email-cli/fetcher"
 11	"github.com/andrinoff/email-cli/sender"
 12	"github.com/andrinoff/email-cli/tui"
 13	tea "github.com/charmbracelet/bubbletea"
 14)
 15
 16// mainModel now holds the state for the entire application.
 17type mainModel struct {
 18	current tea.Model
 19	config  *config.Config
 20	emails  []fetcher.Email
 21	inbox   *tui.Inbox
 22	width   int
 23	height  int
 24	err     error
 25}
 26
 27// newInitialModel now returns a pointer, which is crucial for state management.
 28func newInitialModel(cfg *config.Config) *mainModel {
 29	// If config is nil, it means we're starting from the login screen.
 30	if cfg == nil {
 31		return &mainModel{
 32			current: tui.NewLogin(),
 33		}
 34	}
 35	return &mainModel{
 36		current: tui.NewChoice(),
 37		config:  cfg,
 38	}
 39}
 40
 41func (m *mainModel) Init() tea.Cmd {
 42	return m.current.Init()
 43}
 44
 45func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 46	var cmd tea.Cmd
 47	var cmds []tea.Cmd
 48
 49	switch msg := msg.(type) {
 50	case tea.WindowSizeMsg:
 51		m.width = msg.Width
 52		m.height = msg.Height
 53		// Pass the window size message to the current view
 54		m.current, cmd = m.current.Update(msg)
 55		cmds = append(cmds, cmd)
 56		return m, tea.Batch(cmds...)
 57
 58	case tea.KeyMsg:
 59		if msg.String() == "ctrl+c" {
 60			return m, tea.Quit
 61		}
 62		// Allow ESC to go back
 63		if msg.String() == "esc" {
 64			switch m.current.(type) {
 65			case *tui.EmailView:
 66				m.current = m.inbox
 67				return m, nil
 68			case *tui.Inbox, *tui.Composer:
 69				m.current = tui.NewChoice()
 70				return m, m.current.Init()
 71			}
 72		}
 73
 74	// --- Custom Messages for switching views ---
 75	case tui.Credentials: // This is a struct, not a pointer
 76		cfg := &config.Config{
 77			ServiceProvider: msg.Provider,
 78			Name:            msg.Name,
 79			Email:           msg.Email,
 80			Password:        msg.Password,
 81		}
 82		if err := config.SaveConfig(cfg); err != nil {
 83			// TODO: Show an error message to the user
 84			log.Printf("could not save config: %v", err)
 85			return m, tea.Quit
 86		}
 87		m.config = cfg
 88		m.current = tui.NewChoice()
 89		cmds = append(cmds, m.current.Init())
 90
 91	case tui.GoToInboxMsg:
 92		m.current = tui.NewStatus("Fetching emails...")
 93		return m, tea.Batch(m.current.Init(), fetchEmails(m.config))
 94
 95	case tui.EmailsFetchedMsg:
 96		m.emails = msg.Emails
 97		m.inbox = tui.NewInbox(m.emails)
 98		m.current = m.inbox
 99		// Manually set the size of the new view
100		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
101		cmds = append(cmds, m.current.Init())
102
103	case tui.GoToSendMsg:
104		composer := tui.NewComposer(m.config.Email)
105		m.current = &composer
106		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
107		cmds = append(cmds, m.current.Init())
108
109	case tui.ViewEmailMsg:
110		emailView := tui.NewEmailView(m.emails[msg.Index], m.width, m.height)
111		m.current = emailView
112		cmds = append(cmds, m.current.Init())
113
114	case tui.SendEmailMsg:
115		m.current = tui.NewStatus("Sending email...")
116		cmds = append(cmds, m.current.Init(), sendEmail(m.config, msg))
117
118	case tui.EmailResultMsg:
119		m.current = tui.NewChoice()
120		cmds = append(cmds, m.current.Init())
121	}
122
123	// Pass all other messages to the current view
124	m.current, cmd = m.current.Update(msg)
125	cmds = append(cmds, cmd)
126
127	return m, tea.Batch(cmds...)
128}
129
130func (m *mainModel) View() string {
131	return m.current.View()
132}
133
134// sendEmail is a command that sends an email in the background.
135func sendEmail(cfg *config.Config, msg tui.SendEmailMsg) tea.Cmd {
136	return func() tea.Msg {
137		recipients := []string{msg.To}
138		err := sender.SendEmail(cfg, recipients, msg.Subject, msg.Body)
139		if err != nil {
140			log.Printf("Failed to send email: %v", err) // Log error
141			return tui.EmailResultMsg{Err: err}
142		}
143		time.Sleep(1 * time.Second) // Give user time to see the "Sending" message
144		return tui.EmailResultMsg{}
145	}
146}
147
148func fetchEmails(cfg *config.Config) tea.Cmd {
149	return func() tea.Msg {
150		emails, err := fetcher.FetchEmails(cfg)
151		if err != nil {
152			return tui.FetchErr(err)
153		}
154		return tui.EmailsFetchedMsg{Emails: emails}
155	}
156}
157
158func main() {
159	cfg, err := config.LoadConfig()
160	var initialModel *mainModel
161	if err != nil {
162		// If the config doesn't exist, we can guide the user to create one.
163		initialModel = newInitialModel(nil)
164	} else {
165		initialModel = newInitialModel(cfg)
166	}
167
168	p := tea.NewProgram(initialModel, tea.WithAltScreen())
169
170	if _, err := p.Run(); err != nil {
171		fmt.Printf("Alas, there's been an error: %v", err)
172		os.Exit(1)
173	}
174}