mod app; mod news; mod ui; use std::io; use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use ratatui::{backend::CrosstermBackend, Terminal}; use tokio::sync::mpsc; use app::{App, AppMessage, Tab}; #[tokio::main] async fn main() -> Result<(), Box> { enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; let result = run_app(&mut terminal).await; // Always restore terminal even on error disable_raw_mode()?; execute!( terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture )?; terminal.show_cursor()?; result } async fn run_app( terminal: &mut Terminal>, ) -> Result<(), Box> { let (tx, mut rx) = mpsc::channel::(100); let mut app = App::new(); // Kick off the initial story fetch app.load_stories(tx.clone()); loop { // Drain all pending async messages before drawing while let Ok(msg) = rx.try_recv() { app.handle_message(msg, tx.clone()); } terminal.draw(|f| ui::draw(f, &mut app))?; // Poll for keyboard input with a short timeout so messages keep flowing if event::poll(std::time::Duration::from_millis(200))? { if let Event::Key(key) = event::read()? { match key.code { KeyCode::Char('q') | KeyCode::Esc => break, KeyCode::Down | KeyCode::Char('j') => app.next_story(tx.clone()), KeyCode::Up | KeyCode::Char('k') => app.prev_story(tx.clone()), KeyCode::PageDown => app.scroll_content_down(), KeyCode::PageUp => app.scroll_content_up(), KeyCode::Char('r') => app.load_stories(tx.clone()), KeyCode::Char('s') => app.toggle_save(tx.clone()), KeyCode::Char('1') => app.switch_tab(Tab::TopStories, tx.clone()), KeyCode::Char('2') => app.switch_tab(Tab::Saved, tx.clone()), KeyCode::Right => app.next_source(tx.clone()), KeyCode::Left => app.prev_source(tx.clone()), _ => {} } } } } Ok(()) }