use ratatui::widgets::{ListItem, ListState};
use tracing::error;
use crate::core::library::feedlibrary::FeedLibrary;
pub enum FeedItemInfo {
Category(String),
Item(String, String, String),
Separator,
ReadLater,
}
pub struct FeedTreeState {
pub treeitems: Vec<FeedItemInfo>,
pub listatate: ListState,
}
impl Default for FeedTreeState {
fn default() -> Self {
Self::new()
}
}
impl FeedTreeState {
pub fn new() -> Self {
Self {
treeitems: vec![],
listatate: ListState::default().with_selected(Some(0)),
}
}
pub fn update(&mut self, library: &mut FeedLibrary) {
self.treeitems.clear();
for category in library.feedcategories.iter() {
self.treeitems
.push(FeedItemInfo::Category(category.title.clone()));
for item in category.feeds.iter() {
self.treeitems.push(FeedItemInfo::Item(
item.title.clone(),
category.title.clone(),
item.slug.clone(),
));
}
}
if library.has_read_later_entries() {
self.treeitems.push(FeedItemInfo::Separator);
self.treeitems.push(FeedItemInfo::ReadLater);
}
}
pub fn get_items(&self, library: &mut FeedLibrary) -> Vec<ListItem<'_>> {
self.treeitems
.iter()
.map(|item| {
let title = match item {
FeedItemInfo::Category(t) => format!("\u{f07c} {t}"),
FeedItemInfo::Item(t, c, s) => {
if let Ok(unread) = library.data.get_unread_feed(c, s) {
if unread > 0 {
format!(" \u{f09e} {t} ({unread})")
} else {
format!(" \u{f09e} {t}")
}
} else {
error!("Couldn't get unread feed entries for '{}'", t);
format!(" \u{f09e} {t}")
}
}
FeedItemInfo::Separator => "".to_string(),
FeedItemInfo::ReadLater => {
if let Ok(count) = library.get_read_later_feed_entries() {
format!("\u{f02d} Read Later ({})", count.len())
} else {
"\u{f02d} Read Later".to_string()
}
}
};
ListItem::new(title.clone())
})
.collect()
}
pub fn get_selected(&self) -> Option<&FeedItemInfo> {
if !self.treeitems.is_empty() {
let idx = self.listatate.selected().unwrap_or(0);
let clamped = idx.min(self.treeitems.len().saturating_sub(1));
Some(&self.treeitems[clamped])
} else {
None
}
}
pub fn select_next(&mut self) {
if self.treeitems.is_empty() {
return;
}
let selected = self.listatate.selected().unwrap_or(0);
if selected < self.treeitems.len().saturating_sub(1) {
self.listatate.select_next();
if self.is_selected_separator() {
self.select_next();
}
}
}
pub fn select_previous(&mut self) {
if self.treeitems.is_empty() {
return;
}
let selected = self.listatate.selected().unwrap_or(0);
if selected >= self.treeitems.len() {
self.listatate
.select(Some(self.treeitems.len().saturating_sub(1)));
}
let selected = self.listatate.selected().unwrap_or(0);
if selected > 0 {
self.listatate.select_previous();
if self.is_selected_separator() {
self.select_previous();
}
}
}
pub fn select_first(&mut self) {
if self.treeitems.is_empty() {
return;
}
self.listatate.select_first();
}
pub fn select_last(&mut self) {
if self.treeitems.is_empty() {
return;
}
self.listatate
.select(Some(self.treeitems.len().saturating_sub(1)));
}
fn is_selected_separator(&self) -> bool {
if let Some(index) = self.listatate.selected() {
index < self.treeitems.len() && matches!(self.treeitems[index], FeedItemInfo::Separator)
} else {
false
}
}
}