news 0.1.1

Consume the news in yout terminal 📰
use std::io;

use ratatui::{
    backend::CrosstermBackend,
    layout::{Alignment, Constraint, Direction, Layout, Rect},
    prelude::Margin,
    style::{Color, Modifier, Style, Stylize},
    text::{Line, Span, Text},
    widgets::{
        block::Title, Block, Borders, List, ListItem, ListState, Paragraph, Scrollbar,
        ScrollbarOrientation,
    },
    Frame,
};

use super::app::{App, NewsPage, Page};

pub fn ui(app: &mut App, f: &mut Frame<CrosstermBackend<io::Stdout>>) {
    match &mut app.page {
        Page::Home(_) => {
            let chunks = Layout::default()
                .direction(Direction::Vertical)
                .constraints([Constraint::Percentage(5), Constraint::Percentage(95)].as_ref())
                .split(f.size());

            header(app, f, chunks[0]);
            body(app, f, chunks[1]);
        }
        Page::News(NewsPage {
            news,
            scroll_state,
            scroll_position,
            ..
        }) => {
            let block = Block::default()
                .title(Title::from(news.title.blue().bold()).alignment(Alignment::Center))
                .title(
                    Title::from("j/↓ to go down, k/↑ to go up, esc to go back, q to quit".blue())
                        .alignment(Alignment::Left),
                )
                .borders(Borders::ALL);
            let paragraph = Paragraph::new(news.content.clone())
                .block(block)
                .scroll((*scroll_position, 0));
            f.render_widget(paragraph, f.size());

            f.render_stateful_widget(
                Scrollbar::default()
                    .orientation(ScrollbarOrientation::VerticalLeft)
                    .begin_symbol(Some("↑"))
                    .end_symbol(Some("↓")),
                f.size().inner(&Margin {
                    vertical: 10,
                    horizontal: 10,
                }),
                scroll_state,
            );
        }
    }
}

pub fn body(app: &App, f: &mut Frame<CrosstermBackend<io::Stdout>>, area: Rect) {
    if let Page::Home(home) = &app.page {
        let items: Vec<ListItem> = app
            .categories
            .get(home.selected_index)
            .unwrap()
            .news
            .iter()
            .map(|news| {
                let color = if news.seen {
                    Some(Color::DarkGray)
                } else {
                    None
                };
                let title = Line::from(Span::styled(
                    news.title.clone(),
                    match news.seen {
                        true => Style::default().fg(color.unwrap()),
                        false => Style::default().add_modifier(Modifier::BOLD),
                    },
                ));

                let mut text = Text::from(title);
                if let Some(preview) = news.preview.clone() {
                    let preview_line = Line::from(Span::styled(
                        preview,
                        Style::default().fg(color.unwrap_or(Color::White)),
                    ));
                    text.extend(vec![preview_line])
                }

                let mut info_spans = vec![];

                if let Some(source) = news.source.clone() {
                    let source_span = Span::styled(
                        format!("Source: {}", source),
                        Style::default().fg(color.unwrap_or(Color::Green)),
                    );
                    info_spans.push(source_span)
                }

                if let Some(author) = news.author.clone() {
                    let source_span = Span::styled(
                        format!("Author: {}", author),
                        Style::default().fg(color.unwrap_or(Color::Red)),
                    );
                    info_spans.push(source_span)
                }

                let info_spans: Vec<Span> = info_spans
                    .into_iter()
                    .intersperse(Span::raw(" • "))
                    .collect();

                text.extend(vec![info_spans]);
                text.extend(vec!["\n"]);

                ListItem::new(text)
            })
            .collect();

        let list = List::new(items)
            .block(Block::default())
            .style(Style::default().fg(Color::White))
            .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
            .highlight_symbol(">>");

        let mut state = ListState::default().with_selected(Some(home.selected_news));
        f.render_stateful_widget(list, area, &mut state);
    }
}

pub fn header(app: &App, f: &mut Frame<CrosstermBackend<io::Stdout>>, area: Rect) {
    if let Page::Home(home) = &app.page {
        let tab_count = app.categories.len();
        let tab_width = 100 / tab_count;
        let constraints = vec![Constraint::Percentage(tab_width as u16); tab_count];

        let header = Layout::default()
            .direction(Direction::Horizontal)
            .constraints(constraints)
            .split(area);

        let general = Block::default().borders(Borders::BOTTOM);
        let highlight_style = Style::new().fg(Color::LightBlue);

        app.categories
            .iter()
            .enumerate()
            .for_each(|(index, category)| {
                let paragraph = Paragraph::new(category.name.clone())
                    .alignment(Alignment::Center)
                    .style(if home.selected_index == index {
                        highlight_style
                    } else {
                        Style::new()
                    })
                    .block(general.clone());
                f.render_widget(paragraph, header[index]);
            });
    }
}