use crate::app::App;
use crate::ui::utils::{count_wrapped_lines, format_content_for_reading, truncate_url};
use crate::ui::ColorScheme;
use html2text::from_read;
use ratatui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Padding, Paragraph, Wrap},
Frame,
};
pub(super) fn render_item_detail<B: Backend>(
f: &mut Frame<B>,
app: &mut App,
area: Rect,
colors: &ColorScheme,
) {
if let Some(item) = app.current_item() {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(9), Constraint::Min(0), ])
.split(area);
let mut header_lines = vec![
Line::from(vec![Span::styled(
&item.title,
Style::default()
.fg(colors.text)
.add_modifier(Modifier::BOLD),
)]),
Line::from(""),
];
let mut metadata_parts = Vec::new();
if let (Some(feed_idx), Some(item_idx)) = (app.selected_feed, app.selected_item) {
let is_read = app.is_item_read(feed_idx, item_idx);
metadata_parts.push(Span::styled(
if is_read { "✓ Read" } else { "○ Unread" },
Style::default().fg(if is_read {
colors.success
} else {
colors.highlight
}),
));
}
if let Some(author) = &item.author {
if !metadata_parts.is_empty() {
metadata_parts.push(Span::styled(" · ", Style::default().fg(colors.muted)));
}
metadata_parts.push(Span::styled(
author,
Style::default()
.fg(colors.secondary)
.add_modifier(Modifier::ITALIC),
));
}
if let Some(date) = &item.formatted_date {
if !metadata_parts.is_empty() {
metadata_parts.push(Span::styled(" · ", Style::default().fg(colors.muted)));
}
metadata_parts.push(Span::styled(
date,
Style::default().fg(colors.text_secondary),
));
}
if !metadata_parts.is_empty() {
header_lines.push(Line::from(metadata_parts));
}
header_lines.push(Line::from(""));
if let Some(link) = &item.link {
header_lines.push(Line::from(vec![
Span::styled("🔗 ", Style::default().fg(colors.muted)),
Span::styled(
truncate_url(link, 70),
Style::default()
.fg(colors.primary)
.add_modifier(Modifier::UNDERLINED),
),
]));
}
let article_icon = colors.get_icon_article();
let header = Paragraph::new(header_lines)
.block(
Block::default()
.title(format!(" {} Article ", article_icon))
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_type(colors.border_normal)
.border_style(Style::default().fg(colors.border))
.style(Style::default().bg(colors.surface))
.padding(Padding::new(3, 3, 1, 1)), )
.style(Style::default().fg(colors.text))
.alignment(Alignment::Left);
f.render_widget(header, chunks[0]);
let description = if let Some(desc) = &item.description {
let raw_text = from_read(desc.as_bytes(), 100);
format_content_for_reading(&raw_text)
} else {
"No description available".to_string()
};
let viewport_height = chunks[1]
.height
.saturating_sub(2) .saturating_sub(4);
let content_width = chunks[1]
.width
.saturating_sub(2) .saturating_sub(8) as usize;
let content_lines = count_wrapped_lines(&description, content_width);
app.update_detail_max_scroll(content_lines, viewport_height);
app.clamp_detail_scroll();
let scroll_arrows = if colors.border_normal == BorderType::Double {
("▼", "▲") } else {
("↓", "↑") };
let scroll_indicator = if app.detail_max_scroll > 0 {
let scroll_pct =
(app.detail_vertical_scroll as f32 / app.detail_max_scroll as f32 * 100.0) as u16;
if app.detail_vertical_scroll == 0 {
format!(
" {} Article Content · Scroll {} for more ",
article_icon, scroll_arrows.0
)
} else if app.detail_vertical_scroll >= app.detail_max_scroll {
format!(" {} Article Content · End of article ", article_icon)
} else {
format!(" {} Article Content · {}% ", article_icon, scroll_pct)
}
} else {
format!(" {} Article Content ", article_icon)
};
let content = Paragraph::new(description)
.block(
Block::default()
.title(scroll_indicator)
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_type(colors.border_normal)
.border_style(Style::default().fg(colors.border))
.style(Style::default().bg(colors.surface))
.padding(Padding::new(4, 4, 2, 2)), )
.style(Style::default().fg(colors.text))
.scroll((app.detail_vertical_scroll, 0))
.wrap(Wrap { trim: true })
.alignment(Alignment::Left);
f.render_widget(content, chunks[1]);
}
}