From 4e0f92b8bdf1bae84b4e8d5a6c2db0ec37ba3dc0 Mon Sep 17 00:00:00 2001 From: drew Date: Mon, 28 Jul 2025 12:54:50 +0400 Subject: [PATCH] fix: returned old main.go --- main.go | 237 ++++++++++++++++++-------------------------------------- 1 file changed, 74 insertions(+), 163 deletions(-) diff --git a/main.go b/main.go index 95649aa9f13fe301059c8bad85a34bf8e6d38a67..8243d30ccb569fe447ec6aa1817d2ea376a5df0d 100644 --- a/main.go +++ b/main.go @@ -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) } } \ No newline at end of file