envelope_cli/tui/widgets/
notification.rs1use ratatui::{
6 buffer::Buffer,
7 layout::Rect,
8 style::{Color, Modifier, Style},
9 widgets::{Block, Borders, Clear, Paragraph, Widget},
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum NotificationType {
15 Info,
17 Success,
19 Warning,
21 Error,
23}
24
25impl NotificationType {
26 pub fn color(&self) -> Color {
28 match self {
29 Self::Info => Color::Blue,
30 Self::Success => Color::Green,
31 Self::Warning => Color::Yellow,
32 Self::Error => Color::Red,
33 }
34 }
35
36 pub fn icon(&self) -> &'static str {
38 match self {
39 Self::Info => "i",
40 Self::Success => "+",
41 Self::Warning => "!",
42 Self::Error => "x",
43 }
44 }
45
46 pub fn title(&self) -> &'static str {
48 match self {
49 Self::Info => "Info",
50 Self::Success => "Success",
51 Self::Warning => "Warning",
52 Self::Error => "Error",
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct Notification {
60 pub message: String,
62 pub notification_type: NotificationType,
64 pub created_at: std::time::Instant,
66 pub duration_secs: u64,
68}
69
70impl Notification {
71 pub fn new(message: impl Into<String>, notification_type: NotificationType) -> Self {
73 Self {
74 message: message.into(),
75 notification_type,
76 created_at: std::time::Instant::now(),
77 duration_secs: 3,
78 }
79 }
80
81 pub fn info(message: impl Into<String>) -> Self {
83 Self::new(message, NotificationType::Info)
84 }
85
86 pub fn success(message: impl Into<String>) -> Self {
88 Self::new(message, NotificationType::Success)
89 }
90
91 pub fn warning(message: impl Into<String>) -> Self {
93 Self::new(message, NotificationType::Warning)
94 }
95
96 pub fn error(message: impl Into<String>) -> Self {
98 Self::new(message, NotificationType::Error)
99 }
100
101 pub fn with_duration(mut self, seconds: u64) -> Self {
103 self.duration_secs = seconds;
104 self
105 }
106
107 pub fn is_expired(&self) -> bool {
109 self.created_at.elapsed().as_secs() >= self.duration_secs
110 }
111
112 pub fn remaining_fraction(&self) -> f64 {
114 let elapsed = self.created_at.elapsed().as_secs_f64();
115 let total = self.duration_secs as f64;
116 (1.0 - elapsed / total).clamp(0.0, 1.0)
117 }
118}
119
120pub struct NotificationWidget<'a> {
122 notification: &'a Notification,
123}
124
125impl<'a> NotificationWidget<'a> {
126 pub fn new(notification: &'a Notification) -> Self {
128 Self { notification }
129 }
130}
131
132impl<'a> Widget for NotificationWidget<'a> {
133 fn render(self, area: Rect, buf: &mut Buffer) {
134 let color = self.notification.notification_type.color();
135 let icon = self.notification.notification_type.icon();
136 let title = self.notification.notification_type.title();
137
138 Clear.render(area, buf);
140
141 let block = Block::default()
142 .borders(Borders::ALL)
143 .border_style(Style::default().fg(color))
144 .title(format!(" {} {} ", icon, title))
145 .title_style(Style::default().fg(color).add_modifier(Modifier::BOLD));
146
147 let paragraph = Paragraph::new(self.notification.message.as_str())
148 .style(Style::default().fg(Color::White))
149 .block(block);
150
151 paragraph.render(area, buf);
152 }
153}
154
155#[derive(Debug, Default)]
157pub struct NotificationQueue {
158 notifications: Vec<Notification>,
159}
160
161impl NotificationQueue {
162 pub fn new() -> Self {
164 Self::default()
165 }
166
167 pub fn push(&mut self, notification: Notification) {
169 self.notifications.push(notification);
170 }
171
172 pub fn remove_expired(&mut self) {
174 self.notifications.retain(|n| !n.is_expired());
175 }
176
177 pub fn current(&self) -> Option<&Notification> {
179 self.notifications.first()
180 }
181
182 pub fn is_empty(&self) -> bool {
184 self.notifications.is_empty()
185 }
186
187 pub fn len(&self) -> usize {
189 self.notifications.len()
190 }
191
192 pub fn clear(&mut self) {
194 self.notifications.clear();
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_notification_creation() {
204 let n = Notification::info("Test message");
205 assert_eq!(n.message, "Test message");
206 assert_eq!(n.notification_type, NotificationType::Info);
207 }
208
209 #[test]
210 fn test_notification_types() {
211 assert_eq!(NotificationType::Info.color(), Color::Blue);
212 assert_eq!(NotificationType::Success.color(), Color::Green);
213 assert_eq!(NotificationType::Warning.color(), Color::Yellow);
214 assert_eq!(NotificationType::Error.color(), Color::Red);
215 }
216
217 #[test]
218 fn test_notification_queue() {
219 let mut queue = NotificationQueue::new();
220 assert!(queue.is_empty());
221
222 queue.push(Notification::info("First"));
223 queue.push(Notification::success("Second"));
224
225 assert_eq!(queue.len(), 2);
226 assert_eq!(queue.current().unwrap().message, "First");
227 }
228}