use crate::db::config::Config;
use crate::db::models::{NewTodoList, TodoList, UIList};
use anyhow::Result;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{
Block, BorderType, Borders, HighlightSpacing, List, ListItem, ListState, Padding,
StatefulWidget,
};
use sqlx::SqlitePool;
use std::str::FromStr;
pub struct ListsComponent {
pub lists: Vec<UIList>,
pub list_state: ListState,
}
impl Default for ListsComponent {
fn default() -> Self {
Self::new()
}
}
impl ListsComponent {
pub fn new() -> Self {
Self {
lists: Vec::new(),
list_state: ListState::default(),
}
}
pub async fn load_lists(&mut self, pool: &SqlitePool) -> Result<()> {
self.lists = UIList::get_all(pool).await?;
Ok(())
}
pub fn select_next(&mut self) {
self.list_state.select_next();
}
pub fn select_previous(&mut self) {
self.list_state.select_previous();
}
pub fn selected(&self) -> Option<usize> {
self.list_state.selected()
}
pub fn get_selected_list_mut(&mut self) -> Option<&mut UIList> {
if let Some(i) = self.list_state.selected() {
self.lists.get_mut(i)
} else {
None
}
}
pub fn get_selected_list(&self) -> Option<&UIList> {
if let Some(i) = self.list_state.selected() {
self.lists.get(i)
} else {
None
}
}
pub async fn refresh_lists(&mut self, pool: &SqlitePool) -> Result<()> {
let selected_index = self.list_state.selected();
self.load_lists(pool).await?;
if let Some(index) = selected_index {
if index < self.lists.len() {
self.list_state.select(Some(index));
} else if !self.lists.is_empty() {
self.list_state.select(Some(self.lists.len() - 1));
}
}
Ok(())
}
pub async fn move_selected_list_up(
lists_component: &mut ListsComponent,
pool: &SqlitePool,
) -> Result<()> {
if let Some(i) = lists_component.list_state.selected() {
let mut list = lists_component.lists[i].list.clone();
list.move_up(pool).await?;
lists_component.refresh_lists(pool).await?;
if i > 0 {
lists_component.list_state.select(Some(i - 1));
}
}
Ok(())
}
pub async fn move_selected_list_down(
lists_component: &mut ListsComponent,
pool: &SqlitePool,
) -> Result<()> {
if let Some(i) = lists_component.list_state.selected() {
let mut list = lists_component.lists[i].list.clone();
list.move_down(pool).await?;
lists_component.refresh_lists(pool).await?;
if i + 1 < lists_component.lists.len() {
lists_component.list_state.select(Some(i + 1));
}
}
Ok(())
}
pub async fn delete_selected_list_static(
lists_component: &mut ListsComponent,
pool: &SqlitePool,
) -> Result<()> {
if let Some(i) = lists_component.list_state.selected() {
let list = lists_component.lists[i].list.clone();
list.delete(pool).await?;
lists_component.load_lists(pool).await?;
if lists_component.lists.is_empty() {
lists_component.list_state.select(None);
} else if i >= lists_component.lists.len() {
lists_component
.list_state
.select(Some(lists_component.lists.len() - 1));
}
}
Ok(())
}
pub async fn create_list(
lists_component: &mut ListsComponent,
name: String,
pool: &SqlitePool,
) -> Result<()> {
let new_list = NewTodoList { name };
TodoList::create(pool, new_list).await?;
lists_component.load_lists(pool).await?;
Ok(())
}
pub async fn update_list(
lists_component: &mut ListsComponent,
name: String,
pool: &SqlitePool,
) -> Result<()> {
if let Some(i) = lists_component.list_state.selected() {
let mut list = lists_component.lists[i].list.clone();
list.update_name(pool, name).await?;
lists_component.load_lists(pool).await?;
}
Ok(())
}
pub fn render(&mut self, area: Rect, buf: &mut Buffer, config: Config) {
let fg = config.foreground();
let hl = config.highlight();
let bg = config.background();
let list_command_hints = Line::from(vec![
Span::raw(" "),
Span::styled(" w,s ", Style::default()),
Span::styled("[A]", Style::default().fg(Color::from_str(hl).unwrap())),
Span::styled("dd", Style::default().fg(Color::from_str(fg).unwrap())),
Span::styled(" [D]", Style::default().fg(Color::from_str(hl).unwrap())),
Span::styled("el", Style::default().fg(Color::from_str(fg).unwrap())),
Span::styled(" [M]", Style::default().fg(Color::from_str(hl).unwrap())),
Span::styled("odify ", Style::default().fg(Color::from_str(fg).unwrap())),
Span::raw(" "),
])
.left_aligned();
let block = Block::default()
.padding(Padding::new(2, 2, 1, 1))
.title_top(Line::raw(" L I S T S ").left_aligned())
.title_bottom(list_command_hints)
.title_alignment(Alignment::Center)
.borders(Borders::TOP | Borders::LEFT | Borders::BOTTOM)
.border_type(BorderType::Rounded);
let items: Vec<ListItem> = self
.lists
.iter()
.map(|ui_list| ListItem::from(ui_list.list.name.clone()))
.collect();
let list: List = List::new(items)
.block(block)
.highlight_symbol(" ▸ ") .highlight_style(
Style::default()
.bg(Color::from_str(fg).unwrap())
.fg(Color::from_str(bg).unwrap()),
)
.highlight_spacing(HighlightSpacing::Always);
StatefulWidget::render(list, area, buf, &mut self.list_state);
}
}