rustpm 0.2.2

A fast, friendly APT frontend with kernel, desktop, and sources management
use crossterm::event::KeyCode;
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    widgets::{Block, Borders, Cell, Paragraph, Row, Table, TableState},
    Frame,
};

use crate::sources::parser::AptSource;

pub struct SourcesPanel {
    pub sources: Vec<AptSource>,
    pub state: TableState,
}

impl SourcesPanel {
    pub fn new(sources: Vec<AptSource>) -> Self {
        let mut state = TableState::default();
        if !sources.is_empty() {
            state.select(Some(0));
        }
        Self { sources, state }
    }

    pub fn handle_key(&mut self, code: KeyCode) {
        match code {
            KeyCode::Char('j') | KeyCode::Down => self.next(1),
            KeyCode::Char('k') | KeyCode::Up => self.prev(1),
            KeyCode::PageDown => self.next(10),
            KeyCode::PageUp => self.prev(10),
            _ => {}
        }
    }

    fn next(&mut self, step: usize) {
        let len = self.sources.len();
        if len == 0 { return; }
        let i = self.state.selected().map_or(0, |i| (i + step).min(len - 1));
        self.state.select(Some(i));
    }

    fn prev(&mut self, step: usize) {
        let i = self.state.selected().map_or(0, |i| i.saturating_sub(step));
        self.state.select(Some(i));
    }

    pub fn render(&self, f: &mut Frame, area: Rect) {
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Min(0), Constraint::Length(3)])
            .split(area);

        let rows: Vec<Row> = self
            .sources
            .iter()
            .map(|s| {
                let enabled_marker = if s.enabled { "" } else { "" };
                let components = s.components.join(" ");
                let file = s
                    .file
                    .file_name()
                    .and_then(|n| n.to_str())
                    .unwrap_or("?");

                let style = if s.enabled {
                    Style::default().fg(Color::White)
                } else {
                    Style::default().fg(Color::DarkGray)
                };

                Row::new(vec![
                    Cell::from(enabled_marker),
                    Cell::from(s.source_type.clone()),
                    Cell::from(s.uri.clone()),
                    Cell::from(s.suite.clone()),
                    Cell::from(components),
                    Cell::from(file),
                ])
                .style(style)
            })
            .collect();

        let widths = [
            Constraint::Length(3),
            Constraint::Length(8),
            Constraint::Min(35),
            Constraint::Length(15),
            Constraint::Length(25),
            Constraint::Length(20),
        ];

        let table = Table::new(rows, widths)
            .header(
                Row::new(vec!["", "Type", "URI", "Suite", "Components", "File"])
                    .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
            )
            .block(Block::default().borders(Borders::ALL).title(" APT Sources "))
            .row_highlight_style(Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD))
            .highlight_symbol("");

        let mut state = self.state.clone();
        f.render_stateful_widget(table, chunks[0], &mut state);

        let actions = Paragraph::new("  a: add  d: delete  Space: enable/disable  e: edit in $EDITOR  [?] Help")
            .block(Block::default().borders(Borders::ALL).title(" Actions "));
        f.render_widget(actions, chunks[1]);
    }
}