Files
ui-agregator/src/presentation/notifications.rs
T
2026-05-10 10:59:43 +02:00

149 lines
4.7 KiB
Rust

#![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::<Vec<_>>()
.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(&notif.icon, Style::default().fg(border_color)),
Span::raw(" "),
Span::styled(
&notif.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) = &notif.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(&notif.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) = &notif.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!("{}", &notif.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);
}