vcard_tui 0.1.2

Terminal UI application for managing vCard contacts.
use std::io;
use std::io::Stdout;

use crossterm::event::{DisableMouseCapture, Event, KeyEvent};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
use crossterm::ExecutableCommand;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::Terminal;
use tui_textarea::{CursorMove, TextArea};
use vcard_parser::traits::HasName;
use vcard_parser::vcard::parameter::parameter_type::ParameterTypeData;
use vcard_parser::vcard::parameter::Parameter;
use vcard_parser::vcard::parameter::Parameter::ParameterType;
use vcard_parser::vcard::value::value_listcomponent::ValueListComponentData;

use crate::state::contacts::ContactsState;
use crate::state::StateSelected;
use crate::tui::handler::contact::handle_contact;
use crate::tui::handler::contacts::handle_contacts;
use crate::tui::handler::handle;
use crate::tui::handler::popup::handle_popup;
use crate::tui::render::contact::render_contact;
use crate::tui::render::contacts::render_contacts;
use crate::tui::render::popup::render_popup;
use crate::tui::render::toolbar::render_toolbar;
use crate::State;

mod handler;
mod render;

pub struct Tui {
    terminal: Terminal<CrosstermBackend<Stdout>>,
}

impl From<Terminal<CrosstermBackend<Stdout>>> for Tui {
    fn from(terminal: Terminal<CrosstermBackend<Stdout>>) -> Self {
        Self { terminal }
    }
}

impl Tui {
    pub fn create() -> anyhow::Result<Self> {
        Ok(Tui::from(Terminal::new(CrosstermBackend::new(io::stdout()))?))
    }
    pub fn start(&mut self) -> anyhow::Result<()> {
        enable_raw_mode()?;
        io::stdout().execute(EnterAlternateScreen)?;
        io::stdout().execute(DisableMouseCapture)?;
        self.terminal.hide_cursor()?;
        self.terminal.clear()?;
        Ok(())
    }
    pub fn render(&mut self, state: &mut State) -> anyhow::Result<()> {
        self.terminal.draw(|frame| {
            let rows = Layout::default()
                .direction(Direction::Vertical)
                .constraints([
                    Constraint::Percentage(95),
                    Constraint::Length(3),
                ])
                .split(frame.size());
            let cols = Layout::default()
                .direction(Direction::Horizontal)
                .constraints([
                    Constraint::Percentage(25),
                    Constraint::Percentage(75),
                ])
                .split(rows[0]);

            render_contacts(frame, cols[0], state);
            render_contact(frame, cols[1], state);
            render_toolbar(frame, rows[1], state);
            if let Some(popup) = state.popup.as_mut() {
                render_popup(frame, cols[1], popup)
            }
        })?;
        Ok(())
    }
    pub fn input(&self, event: &Event, state: &mut State) -> anyhow::Result<()> {
        if state.popup.is_some() {
            handle_popup(event, state)?;
        } else {
            handle(event, state)?;
            match state.selected.get() {
                StateSelected::Contacts => handle_contacts(event, state)?,
                StateSelected::Contact => handle_contact(event, state)?,
            }
        }
        Ok(())
    }
    pub fn end(&mut self) -> anyhow::Result<()> {
        self.terminal.clear()?;
        self.terminal.show_cursor()?;
        io::stdout().execute(LeaveAlternateScreen)?;
        disable_raw_mode()?;
        Ok(())
    }
}

pub trait KeyEventHandler {
    fn handle_key_event(&mut self, key_event: &KeyEvent, state: &mut ContactsState) -> anyhow::Result<bool>;
}

pub trait HasValueListComponent {
    fn value_data_get(&self) -> ValueListComponentData;
    fn value_data_set(&mut self, data: ValueListComponentData);
    fn value_listcomponent_get_string(&self, i: usize) -> String {
        if let Some(v) = self.value_data_get().value.get(i) {
            if let Some(v) = v.get(0) {
                return v.to_string();
            }
        }
        String::new()
    }
    fn value_listcomponent_set_string(&mut self, i: usize, string: String) {
        let mut data = self.value_data_get().clone();
        data.value[i] = Vec::from([string]);
        self.value_data_set(data);
    }
}

pub trait HasTextArea<'a> {
    fn textarea_value_get(&self) -> String;
    fn textarea_value_set(&mut self, string: String);
    fn textarea_get(&self) -> &Option<TextArea>;
    fn textarea_set(&mut self, textarea: Option<TextArea<'a>>);
    fn toggle_textarea(&mut self) {
        if let Some(textarea) = self.textarea_get() {
            self.textarea_value_set(textarea.lines().first().unwrap().to_string());
            self.textarea_set(None)
        } else {
            let mut textarea = TextArea::new(Vec::from([self.textarea_value_get()]));
            textarea.move_cursor(CursorMove::End);
            self.textarea_set(Some(textarea))
        }
    }
}

pub trait HasTypeParam {
    fn parameters_get(&self) -> Vec<Parameter>;
    fn parameters_set(&mut self, parameters: Vec<Parameter>);
    fn type_param_get(&self) -> Parameter {
        self.parameters_get()
            .iter()
            .find(|p| p.name() == "TYPE")
            .cloned()
            .unwrap_or(ParameterType(ParameterTypeData::default()))
    }
    fn type_param_set(&mut self, string: String) {
        let mut parameters = self
            .parameters_get()
            .into_iter()
            .filter(|p| p.name() != "TYPE")
            .collect::<Vec<Parameter>>();
        parameters.push(ParameterType(
            ParameterTypeData::try_from(string.as_str()).unwrap_or(ParameterTypeData::default()),
        ));
        self.parameters_set(parameters);
    }
}