use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier, Style},
widgets::{Block, Borders, Clear, Paragraph, Widget},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NotificationType {
Info,
Success,
Warning,
Error,
}
impl NotificationType {
pub fn color(&self) -> Color {
match self {
Self::Info => Color::Blue,
Self::Success => Color::Green,
Self::Warning => Color::Yellow,
Self::Error => Color::Red,
}
}
pub fn icon(&self) -> &'static str {
match self {
Self::Info => "i",
Self::Success => "+",
Self::Warning => "!",
Self::Error => "x",
}
}
pub fn title(&self) -> &'static str {
match self {
Self::Info => "Info",
Self::Success => "Success",
Self::Warning => "Warning",
Self::Error => "Error",
}
}
}
#[derive(Debug, Clone)]
pub struct Notification {
pub message: String,
pub notification_type: NotificationType,
pub created_at: std::time::Instant,
pub duration_secs: u64,
}
impl Notification {
pub fn new(message: impl Into<String>, notification_type: NotificationType) -> Self {
Self {
message: message.into(),
notification_type,
created_at: std::time::Instant::now(),
duration_secs: 3,
}
}
pub fn info(message: impl Into<String>) -> Self {
Self::new(message, NotificationType::Info)
}
pub fn success(message: impl Into<String>) -> Self {
Self::new(message, NotificationType::Success)
}
pub fn warning(message: impl Into<String>) -> Self {
Self::new(message, NotificationType::Warning)
}
pub fn error(message: impl Into<String>) -> Self {
Self::new(message, NotificationType::Error)
}
pub fn with_duration(mut self, seconds: u64) -> Self {
self.duration_secs = seconds;
self
}
pub fn is_expired(&self) -> bool {
self.created_at.elapsed().as_secs() >= self.duration_secs
}
pub fn remaining_fraction(&self) -> f64 {
let elapsed = self.created_at.elapsed().as_secs_f64();
let total = self.duration_secs as f64;
(1.0 - elapsed / total).clamp(0.0, 1.0)
}
}
pub struct NotificationWidget<'a> {
notification: &'a Notification,
}
impl<'a> NotificationWidget<'a> {
pub fn new(notification: &'a Notification) -> Self {
Self { notification }
}
}
impl<'a> Widget for NotificationWidget<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let color = self.notification.notification_type.color();
let icon = self.notification.notification_type.icon();
let title = self.notification.notification_type.title();
Clear.render(area, buf);
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(color))
.title(format!(" {} {} ", icon, title))
.title_style(Style::default().fg(color).add_modifier(Modifier::BOLD));
let paragraph = Paragraph::new(self.notification.message.as_str())
.style(Style::default().fg(Color::White))
.block(block);
paragraph.render(area, buf);
}
}
#[derive(Debug, Default)]
pub struct NotificationQueue {
notifications: Vec<Notification>,
}
impl NotificationQueue {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, notification: Notification) {
self.notifications.push(notification);
}
pub fn remove_expired(&mut self) {
self.notifications.retain(|n| !n.is_expired());
}
pub fn current(&self) -> Option<&Notification> {
self.notifications.first()
}
pub fn is_empty(&self) -> bool {
self.notifications.is_empty()
}
pub fn len(&self) -> usize {
self.notifications.len()
}
pub fn clear(&mut self) {
self.notifications.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_notification_creation() {
let n = Notification::info("Test message");
assert_eq!(n.message, "Test message");
assert_eq!(n.notification_type, NotificationType::Info);
}
#[test]
fn test_notification_types() {
assert_eq!(NotificationType::Info.color(), Color::Blue);
assert_eq!(NotificationType::Success.color(), Color::Green);
assert_eq!(NotificationType::Warning.color(), Color::Yellow);
assert_eq!(NotificationType::Error.color(), Color::Red);
}
#[test]
fn test_notification_queue() {
let mut queue = NotificationQueue::new();
assert!(queue.is_empty());
queue.push(Notification::info("First"));
queue.push(Notification::success("Second"));
assert_eq!(queue.len(), 2);
assert_eq!(queue.current().unwrap().message, "First");
}
}