use std::fmt::Debug;
use crate::api::model::MangaMediaType;
use crate::api::model::Node;
use crate::api::model::PageableData;
use crate::api::model::UserReadStatus;
use crate::app::DISPLAY_COLUMN_NUMBER;
use crate::app::DISPLAY_RAWS_NUMBER;
use crate::ui::format_number_with_commas;
use crate::ui::get_end_card_index;
use crate::{
api::model::{AnimeMediaType, UserWatchStatus},
app::{ActiveBlock, App, SelectedSearchTab},
ui::util::get_color,
};
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
Frame,
};
use super::get_anime_status_color;
pub fn draw_results(f: &mut Frame, app: &App, chunk: Rect) {
match app.search_results.selected_tab {
SelectedSearchTab::Anime => {
if app.search_results.anime.as_ref().is_some() {
draw_anime_search_results(f, app, chunk);
} else {
}
}
SelectedSearchTab::Manga => {
if app.search_results.manga.as_ref().is_some() {
draw_manga_search_results(f, app, chunk);
} else {
}
}
}
}
pub fn draw_anime_search_results(f: &mut Frame, app: &App, chunk: Rect) {
let results = app.search_results.anime.as_ref().unwrap();
if results.data.is_empty() {
return;
}
let start_index = app.start_card_list_index as usize;
let end_index = get_end_card_index(app);
let (cards, components_result) = construct_cards_with_data(chunk, results);
let component_page = components_result[start_index..=end_index].to_vec();
let selected_card_index = app.search_results.selected_display_card_index.unwrap_or(0);
for (index, component) in component_page.iter().enumerate() {
let is_active =
index == selected_card_index && app.active_block == ActiveBlock::DisplayBlock;
let anime_status = component
.my_list_status
.as_ref()
.map_or(UserWatchStatus::Other("None".to_string()), |status| {
status.status.clone()
});
let anime_status_color = get_anime_status_color(&anime_status, app);
let anime_status: &str = anime_status.into();
let title_style = get_color(is_active, app.app_config.theme);
let anime_title = &component.get_title(&app.app_config, false)[0];
let title: Line<'_> = Line::from(vec![
Span::styled(anime_title, title_style.add_modifier(Modifier::BOLD)),
Span::raw(" "),
Span::styled(anime_status, Style::default().fg(anime_status_color)),
]);
let media_type: &str = Into::<&str>::into(
component
.media_type
.as_ref()
.map_or(AnimeMediaType::Other("Unknown".to_string()), |media_type| {
media_type.clone()
}),
);
let ep_num: String = component.num_episodes.map_or("N/A".to_string(), |ep| {
if ep != 0 {
ep.to_string()
} else {
"N/A".to_string()
}
});
let start_date: String = component
.start_date
.as_ref()
.map_or("unknown".to_string(), |date| date.date.year().to_string());
let num_user_list: String = component
.num_list_users
.map_or("N/A".to_string(), format_number_with_commas);
let score = Line::from(Span::styled(
format!(
"Scored {}",
component.mean.map_or("N/A".to_string(), |m| m.to_string())
),
Style::default(), ));
let num_ep = Line::from(Span::styled(
format!("{} ({} eps)", media_type, ep_num),
app.app_config.theme.text,
));
let start_date = Line::from(Span::styled(start_date, app.app_config.theme.text));
let num_user_list = Line::from(Span::styled(
format!("{} members", num_user_list),
app.app_config.theme.text,
));
let card = Paragraph::new(vec![title, num_ep, score, start_date, num_user_list])
.alignment(Alignment::Left)
.wrap(ratatui::widgets::Wrap { trim: true })
.block(
Block::default()
.borders(Borders::ALL)
.border_style(get_color(is_active, app.app_config.theme)),
);
f.render_widget(card, cards[index]);
}
}
pub fn draw_manga_search_results(f: &mut Frame, app: &App, chunk: Rect) {
let results = app.search_results.manga.as_ref().unwrap();
if results.data.is_empty() {
return;
}
let start_index = app.start_card_list_index as usize;
let end_index = get_end_card_index(app);
let (cards, components_result) = construct_cards_with_data(chunk, results);
let component_page = components_result[start_index..=end_index].to_vec();
let selected_card_index = app.search_results.selected_display_card_index.unwrap_or(0);
for (index, component) in component_page.iter().enumerate() {
if index >= cards.len() {
break;
}
let is_active =
index == selected_card_index && app.active_block == ActiveBlock::DisplayBlock;
let manga_status = component
.my_list_status
.as_ref()
.map_or(UserReadStatus::Other("None".to_string()), |status| {
status.status.clone()
});
let manga_status_color = get_manga_status_color(&manga_status, app);
let title_style = get_color(is_active, app.app_config.theme);
let title = &component.get_title(&app.app_config, false)[0];
let title: Line<'_> = Line::from(vec![
Span::styled(title, title_style.add_modifier(Modifier::BOLD)),
Span::raw(" "),
Span::styled::<&str, ratatui::prelude::Style>(
manga_status.into(),
Style::default().fg(manga_status_color),
),
]);
let media_type: &str = Into::<&str>::into(
component
.media_type
.as_ref()
.map_or(MangaMediaType::Other("None".to_string()), |media_type| {
media_type.clone()
}),
);
let vol_num: String = component.get_num(&app.app_config);
let start_date: String = component
.start_date
.as_ref()
.map_or("unknown".to_string(), |date| date.date.year().to_string());
let score = Line::from(Span::styled(
format!(
"Scored {}",
component.mean.map_or("N/A".to_string(), |m| m.to_string())
),
Style::default(), ));
let num_user_list: String = component
.num_list_users
.map_or("N/A".to_string(), format_number_with_commas);
let type_num_vol = Line::from(Span::styled(
format!("{} ({})", media_type, vol_num),
app.app_config.theme.text,
));
let start_date = Line::from(Span::styled(start_date, app.app_config.theme.text));
let num_user_list = Line::from(Span::styled(
format!("{} members", num_user_list),
app.app_config.theme.text,
));
let card = Paragraph::new(vec![title, type_num_vol, score, start_date, num_user_list])
.alignment(Alignment::Left)
.wrap(ratatui::widgets::Wrap { trim: true })
.block(
Block::default()
.borders(Borders::ALL)
.border_style(get_color(is_active, app.app_config.theme)),
);
f.render_widget(card, cards[index]);
}
}
pub fn construct_cards_with_data<T: Clone + Debug>(
chunk: Rect,
results: &PageableData<Vec<Node<T>>>,
) -> (Vec<Rect>, Vec<&T>) {
let current_page = &results.data;
let raw_layout = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints(vec![Constraint::Percentage(20); DISPLAY_RAWS_NUMBER])
.split(chunk);
let components: Vec<&T> = current_page.iter().map(|node| &node.node).collect();
(
raw_layout
.iter()
.flat_map(|raw| {
Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![
Constraint::Ratio(1, DISPLAY_COLUMN_NUMBER as u32);
DISPLAY_COLUMN_NUMBER
])
.split(*raw)
.iter()
.copied()
.collect::<Vec<Rect>>()
})
.collect(),
components,
)
}
fn get_manga_status_color(status: &UserReadStatus, app: &App) -> Color {
match status {
UserReadStatus::Completed => app.app_config.theme.status_completed,
UserReadStatus::Dropped => app.app_config.theme.status_dropped,
UserReadStatus::OnHold => app.app_config.theme.status_on_hold,
UserReadStatus::PlanToRead => app.app_config.theme.status_plan_to_watch,
UserReadStatus::Reading => app.app_config.theme.status_watching,
UserReadStatus::Other(_) => app.app_config.theme.status_other,
}
}