manual refactoring

This commit is contained in:
Shautvast 2026-02-17 19:58:04 +01:00
parent 9bbfad554e
commit 05aa47d723
2 changed files with 146 additions and 131 deletions

View file

@ -1 +1,142 @@
use std::io::{Error, Stdout};
use std::net::TcpStream;
use std::time::{Duration, Instant};
use crossterm::{event};
use crossterm::event::{Event, KeyCode};
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::prelude::{Color, Modifier, Style};
use ratatui::Terminal;
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
use crate::config::Config;
pub mod config;
const POLL_INTERVAL: Duration = Duration::from_secs(30);
pub(crate) struct Email {
subject: String,
from: String,
date: String,
}
pub fn main(config: &Config, mut terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Error>{
let mut inbox = fetch_inbox(config);
let mut last_fetch = Instant::now();
// --- Main loop ---
loop {
if last_fetch.elapsed() >= POLL_INTERVAL {
inbox = fetch_inbox(&config);
last_fetch = Instant::now();
}
terminal.draw(|frame| {
let area = frame.area();
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(1)])
.split(area);
match &inbox {
Ok(emails) if !emails.is_empty() => {
let items: Vec<ListItem> = emails
.iter()
.map(|e| {
ListItem::new(format!("{} | {} | {}", e.date, e.from, e.subject))
})
.collect();
let list = List::new(items)
.block(
Block::default()
.title(format!("Inbox ({} messages)", emails.len()))
.borders(Borders::ALL),
)
.style(Style::default().fg(Color::White))
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
frame.render_widget(list, layout[0]);
}
Ok(_) => {
let p = Paragraph::new("No messages in inbox.")
.block(Block::default().title("Inbox").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow));
frame.render_widget(p, layout[0]);
}
Err(e) => {
let p = Paragraph::new(format!("IMAP error: {}", e))
.block(Block::default().title("Inbox").borders(Borders::ALL))
.style(Style::default().fg(Color::Red));
frame.render_widget(p, layout[0]);
}
}
let status = Paragraph::new(" 'q' quit | 'r' refresh")
.style(Style::default().fg(Color::DarkGray));
frame.render_widget(status, layout[1]);
})?;
// --- Input handling ---
if event::poll(Duration::from_millis(200))? {
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Char('r') => {
inbox = fetch_inbox(&config);
last_fetch = Instant::now();
}
_ => {}
}
}
}
}
Ok(())
}
pub(crate) fn fetch_inbox(config: &Config) -> Result<Vec<Email>, String> {
let imap_cfg = &config.imap;
let stream =
TcpStream::connect((&*imap_cfg.host, imap_cfg.port)).map_err(|e| e.to_string())?;
let client = imap::Client::new(stream);
let mut session = client
.login(&imap_cfg.username, &imap_cfg.password)
.map_err(|(e, _)| e.to_string())?;
session.select("INBOX").map_err(|e| e.to_string())?;
let mut emails = Vec::new();
let messages = session
.fetch("1:*", "BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]")
.map_err(|e| e.to_string())?;
for message in messages.iter() {
if let Some(body) = message.header() {
let header = String::from_utf8_lossy(body);
let mut subject = String::new();
let mut from = String::new();
let mut date = String::new();
for line in header.lines() {
if let Some(val) = line.strip_prefix("Subject: ") {
subject = val.to_string();
} else if let Some(val) = line.strip_prefix("From: ") {
from = val.to_string();
} else if let Some(val) = line.strip_prefix("Date: ") {
date = val.to_string();
}
}
emails.push(Email {
subject,
from,
date,
});
}
}
let _ = session.logout();
Ok(emails)
}

View file

@ -1,153 +1,27 @@
use std::{io, net::TcpStream, time::{Duration, Instant}};
use std::io;
use crossterm::{
event::{self, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use hello_ratatui::config::Config;
use ratatui::{
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style},
widgets::{Block, Borders, List, ListItem, Paragraph},
Terminal,
};
use hello_ratatui::config::Config;
struct Email {
subject: String,
from: String,
date: String,
}
fn fetch_inbox(config: &Config) -> Result<Vec<Email>, String> {
let imap_cfg = &config.imap;
let stream =
TcpStream::connect((&*imap_cfg.host, imap_cfg.port)).map_err(|e| e.to_string())?;
let client = imap::Client::new(stream);
let mut session = client
.login(&imap_cfg.username, &imap_cfg.password)
.map_err(|(e, _)| e.to_string())?;
session.select("INBOX").map_err(|e| e.to_string())?;
let mut emails = Vec::new();
let messages = session
.fetch("1:*", "BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]")
.map_err(|e| e.to_string())?;
for message in messages.iter() {
if let Some(body) = message.header() {
let header = String::from_utf8_lossy(body);
let mut subject = String::new();
let mut from = String::new();
let mut date = String::new();
for line in header.lines() {
if let Some(val) = line.strip_prefix("Subject: ") {
subject = val.to_string();
} else if let Some(val) = line.strip_prefix("From: ") {
from = val.to_string();
} else if let Some(val) = line.strip_prefix("Date: ") {
date = val.to_string();
}
}
emails.push(Email {
subject,
from,
date,
});
}
}
let _ = session.logout();
Ok(emails)
}
const POLL_INTERVAL: Duration = Duration::from_secs(30);
fn main() -> io::Result<()> {
let config = Config::load().unwrap();
let mut inbox = fetch_inbox(&config);
let mut last_fetch = Instant::now();
// --- Setup terminal ---
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
enable_raw_mode()?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// --- Main loop ---
loop {
if last_fetch.elapsed() >= POLL_INTERVAL {
inbox = fetch_inbox(&config);
last_fetch = Instant::now();
}
terminal.draw(|frame| {
let area = frame.area();
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(1)])
.split(area);
match &inbox {
Ok(emails) if !emails.is_empty() => {
let items: Vec<ListItem> = emails
.iter()
.map(|e| {
ListItem::new(format!("{} | {} | {}", e.date, e.from, e.subject))
})
.collect();
let list = List::new(items)
.block(
Block::default()
.title(format!("Inbox ({} messages)", emails.len()))
.borders(Borders::ALL),
)
.style(Style::default().fg(Color::White))
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
frame.render_widget(list, layout[0]);
}
Ok(_) => {
let p = Paragraph::new("No messages in inbox.")
.block(Block::default().title("Inbox").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow));
frame.render_widget(p, layout[0]);
}
Err(e) => {
let p = Paragraph::new(format!("IMAP error: {}", e))
.block(Block::default().title("Inbox").borders(Borders::ALL))
.style(Style::default().fg(Color::Red));
frame.render_widget(p, layout[0]);
}
}
let status = Paragraph::new(" 'q' quit | 'r' refresh")
.style(Style::default().fg(Color::DarkGray));
frame.render_widget(status, layout[1]);
})?;
// --- Input handling ---
if event::poll(Duration::from_millis(200))? {
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Char('r') => {
inbox = fetch_inbox(&config);
last_fetch = Instant::now();
}
_ => {}
}
}
}
}
hello_ratatui::main(&config, &mut terminal)?;
// --- Restore terminal ---
disable_raw_mode()?;