#![allow(dead_code)] use std::time::Instant; use ratatui::{ Frame, layout::Rect, style::Style, text::{Line, Span}, widgets::{Block, Borders, Clear, Paragraph}, }; use crate::application::notification_state::{ MAX_VISIBLE, Notification, NotificationManager, format_elapsed, }; use crate::theme; impl NotificationManager { pub fn render(&self, frame: &mut Frame, area: Rect) { let visible: Vec<&Notification> = self .active() .iter() .rev() .take(MAX_VISIBLE) .collect::>() .into_iter() .rev() .collect(); if visible.is_empty() { return; } let notif_width = 50u16.min(area.width.saturating_sub(4)); let notif_height = 3u16; let spacing = 1u16; let total_height = visible.len() as u16 * (notif_height + spacing); let start_y = area.y + area.height.saturating_sub(total_height + 1); let start_x = area.x + area.width.saturating_sub(notif_width + 2); for (i, notif) in visible.iter().enumerate() { let y = start_y + (i as u16) * (notif_height + spacing); let notif_area = Rect::new(start_x, y, notif_width, notif_height); frame.render_widget(Clear, notif_area); let border_color = notif.kind.color(); let block = Block::default() .borders(Borders::LEFT) .border_style(Style::default().fg(border_color)) .style(Style::default().bg(theme::BG1)); let inner = block.inner(notif_area); frame.render_widget(block, notif_area); let elapsed = Instant::now().duration_since(notif.created_at).as_secs(); let timestamp = if elapsed == 0 { "now".to_string() } else { format!("{}s", elapsed) }; let mut lines = vec![Line::from(vec![ Span::styled(¬if.icon, Style::default().fg(border_color)), Span::raw(" "), Span::styled( ¬if.title, Style::default() .fg(theme::FG1) .add_modifier(ratatui::style::Modifier::BOLD), ), Span::raw(" "), Span::styled(timestamp, Style::default().fg(theme::GRAY)), ])]; if let Some(detail) = ¬if.detail { let max_len = inner.width.saturating_sub(2) as usize; let d = if detail.len() > max_len { format!("{}…", &detail[..max_len.saturating_sub(1)]) } else { detail.clone() }; lines.push(Line::from(Span::styled( d, Style::default().fg(theme::GRAY), ))); } let para = Paragraph::new(lines); frame.render_widget(para, inner); } } } pub fn render_notification_item(frame: &mut Frame, area: Rect, notif: &Notification) { let border_color = notif.kind.color(); let block = Block::default() .borders(Borders::LEFT) .border_style(Style::default().fg(border_color)) .style(Style::default().bg(theme::BG1)); let inner = block.inner(area); frame.render_widget(block, area); let elapsed = Instant::now().duration_since(notif.created_at).as_secs(); let timestamp = format_elapsed(elapsed); let mut lines = vec![Line::from(vec![ Span::styled(¬if.icon, Style::default().fg(border_color)), Span::raw(" "), Span::styled( notif.kind.label(), Style::default() .fg(theme::FG1) .add_modifier(ratatui::style::Modifier::BOLD), ), Span::raw(" "), Span::styled(timestamp, Style::default().fg(theme::GRAY)), ])]; if let Some(detail) = ¬if.detail { let max_len = inner.width.saturating_sub(2) as usize; let d = if detail.len() > max_len { format!("{}…", &detail[..max_len.saturating_sub(1)]) } else { detail.clone() }; lines.push(Line::from(Span::styled( d, Style::default().fg(theme::GRAY), ))); } else { let max_len = inner.width.saturating_sub(2) as usize; let title = if notif.title.len() > max_len { format!("{}…", ¬if.title[..max_len.saturating_sub(1)]) } else { notif.title.clone() }; lines.push(Line::from(Span::styled( title, Style::default().fg(theme::GRAY), ))); } let para = Paragraph::new(lines); frame.render_widget(para, inner); }