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