@@ -1,211 +1,122 @@
package main
import (
+ "fmt"
"log"
"os"
"time"
- // These imports were missing, causing all the "undefined" errors.
"github.com/andrinoff/email-cli/config"
- "github.com/andrinoff/email-cli/fetcher"
"github.com/andrinoff/email-cli/sender"
"github.com/andrinoff/email-cli/tui"
-
tea "github.com/charmbracelet/bubbletea"
)
-type modelState int
-
-const (
- loginView modelState = iota
- mainMenu
- inboxView
- emailView
- composeView
-)
-
-// Model holds the application's state.
-type Model struct {
- state modelState
- login tea.Model
- mainMenu tea.Model
- inbox tea.Model
- emailView tea.Model
- composer tea.Model
- fetcher *fetcher.Fetcher
- sender *sender.Sender
- emails []fetcher.Email
- statusMessage string
- err error
+// mainModel now holds the state for the entire application.
+type mainModel struct {
+ current tea.Model
+ config *config.Config
+ width int
+ height int
+ err error
}
-// New creates the initial model.
-func New() Model {
- // We start with the login view.
- return Model{
- state: loginView,
- login: tui.NewLogin(),
- mainMenu: tui.NewChoice(),
+// newInitialModel now returns a pointer, which is crucial for state management.
+func newInitialModel(cfg *config.Config) *mainModel {
+ return &mainModel{
+ current: tui.NewChoice(),
+ config: cfg,
}
}
-// Init runs any initial commands.
-func (m Model) Init() tea.Cmd {
- return tea.EnterAltScreen
+func (m *mainModel) Init() tea.Cmd {
+ return m.current.Init()
}
-// Update handles all messages and updates the model.
-func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd
- // Global messages that can be handled regardless of state.
switch msg := msg.(type) {
+ case tea.WindowSizeMsg:
+ m.width = msg.Width
+ m.height = msg.Height
+ // Pass the window size message to the current view
+ m.current, cmd = m.current.Update(msg)
+ cmds = append(cmds, cmd)
+ return m, tea.Batch(cmds...)
+
case tea.KeyMsg:
if msg.String() == "ctrl+c" {
return m, tea.Quit
}
- case tui.EmailResultMsg:
- if msg.Err != nil {
- m.statusMessage = "❌ Error: " + msg.Err.Error()
- } else {
- m.statusMessage = "✅ Email sent successfully!"
- }
- m.state = mainMenu
- return m, tea.Tick(3*time.Second, func(t time.Time) tea.Msg {
- return tui.ClearStatusMsg{}
- })
- case tui.ClearStatusMsg:
- m.statusMessage = ""
- return m, nil
- }
-
- // State-specific message handling.
- switch m.state {
- case loginView:
- // When the user submits their credentials...
- if msg, ok := msg.(tui.Credentials); ok {
- // For now, we'll default to Gmail.
- service := "gmail"
- cfg := &config.Config{Email: msg.Email, Password: msg.Password, Service: service}
- config.Save(cfg)
- m.fetcher = fetcher.New(cfg)
- m.sender = sender.New(msg.Email, msg.Password, service)
- m.state = mainMenu
- return m, nil
- }
-
- case mainMenu:
- // Handle user's choice from the main menu.
- if msg, ok := msg.(tea.KeyMsg); ok {
- switch msg.String() {
- case "1", "j", "down": // View Inbox
- m.state = inboxView
- m.inbox = tui.NewInbox(m.emails)
- return m, func() tea.Msg {
- emails, err := m.fetcher.FetchEmails("INBOX")
- if err != nil {
- return tui.FetchErr(err)
- }
- return tui.EmailsFetchedMsg{Emails: emails}
- }
- case "2", "k", "up": // Compose Email
- m.state = composeView
- m.composer = tui.NewComposer(m.fetcher.Config.Email)
- return m, nil
- }
- }
-
- case inboxView:
- switch msg := msg.(type) {
- case tui.EmailsFetchedMsg:
- m.emails = msg.Emails
- m.inbox = tui.NewInbox(m.emails)
- return m, nil
- case tui.ViewEmailMsg:
- m.state = emailView
- m.emailView = tui.NewEmailView(m.emails[msg.Index])
- return m, nil
- case tea.KeyMsg:
- if msg.String() == "esc" {
- m.state = mainMenu
+ // Allow ESC to go back to the main menu
+ if msg.String() == "esc" {
+ if _, ok := m.current.(*tui.Choice); !ok {
+ m.current = tui.NewChoice()
+ return m, m.current.Init()
}
}
- case emailView:
- if msg, ok := msg.(tea.KeyMsg); ok && msg.String() == "esc" {
- m.state = inboxView // Go back to the inbox list.
- }
-
- case composeView:
- switch msg := msg.(type) {
- case tui.SendEmailMsg:
- m.statusMessage = "⏳ Sending email..."
- return m, func() tea.Msg {
- err := m.sender.Send(msg.To, msg.Subject, msg.Body)
- return tui.EmailResultMsg{Err: err}
- }
- case tea.KeyMsg:
- if msg.String() == "esc" {
- m.state = mainMenu
- }
- }
+ // --- Custom Messages for switching views ---
+ case tui.GoToInboxMsg:
+ m.current = tui.NewInbox()
+ // Manually set the size of the new view
+ m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
+ cmds = append(cmds, m.current.Init())
+
+ case tui.GoToSendMsg:
+ m.current = tui.NewComposer(m.config.Email)
+ m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
+ cmds = append(cmds, m.current.Init())
+
+ case tui.ViewEmailMsg:
+ m.current = tui.NewEmailView(msg.Email, m.width, m.height)
+ cmds = append(cmds, m.current.Init())
+
+ case tui.SendEmailMsg:
+ m.current = tui.NewStatus("Sending email...")
+ cmds = append(cmds, m.current.Init(), sendEmail(m.config, msg))
+
+ case tui.EmailSentMsg:
+ m.current = tui.NewChoice()
+ cmds = append(cmds, m.current.Init())
}
- // Pass the message to the current view's Update function.
- switch m.state {
- case loginView:
- m.login, cmd = m.login.Update(msg)
- case mainMenu:
- m.mainMenu, cmd = m.mainMenu.Update(msg)
- case inboxView:
- m.inbox, cmd = m.inbox.Update(msg)
- case emailView:
- m.emailView, cmd = m.emailView.Update(msg)
- case composeView:
- m.composer, cmd = m.composer.Update(msg)
- }
+ // Pass all other messages to the current view
+ m.current, cmd = m.current.Update(msg)
cmds = append(cmds, cmd)
+
return m, tea.Batch(cmds...)
}
-// View renders the current UI.
-func (m Model) View() string {
- var s string
- switch m.state {
- case loginView:
- s = m.login.View()
- case mainMenu:
- s = m.mainMenu.View()
- case inboxView:
- s = m.inbox.View()
- case emailView:
- s = m.emailView.View()
- case composeView:
- s = m.composer.View()
- default:
- s = "Unknown state."
- }
+func (m *mainModel) View() string {
+ return m.current.View()
+}
- if m.statusMessage != "" {
- return s + "\n\n" + m.statusMessage
+// sendEmail is a command that sends an email in the background.
+func sendEmail(cfg *config.Config, msg tui.SendEmailMsg) tea.Cmd {
+ return func() tea.Msg {
+ recipients := []string{msg.To}
+ err := sender.SendEmail(cfg, recipients, msg.Subject, msg.Body)
+ if err != nil {
+ log.Printf("Failed to send email: %v", err) // Log error
+ }
+ time.Sleep(1 * time.Second) // Give user time to see the "Sending" message
+ return tui.EmailSentMsg{}
}
- return s
}
func main() {
- // Load config first to see if we can skip login.
- cfg := config.Load()
- m := New()
- if cfg.Email != "" && cfg.Password != "" && cfg.Service != "" {
- m.fetcher = fetcher.New(cfg)
- m.sender = sender.New(cfg.Email, cfg.Password, cfg.Service)
- m.state = mainMenu
+ cfg, err := config.LoadConfig()
+ if err != nil {
+ log.Fatalf("could not load config: %v", err)
}
- p := tea.NewProgram(m)
+ p := tea.NewProgram(newInitialModel(cfg), tea.WithAltScreen())
+
if _, err := p.Run(); err != nil {
- log.Fatal(err)
+ fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
}