Skip to main content

oxihuman_core/
notification_system.rs

1//! In-app notification system with severity and dismissal.
2
3#[allow(dead_code)]
4#[derive(Clone, PartialEq, Debug)]
5pub enum NotificationSeverity {
6    Info,
7    Warning,
8    Error,
9    Success,
10}
11
12#[allow(dead_code)]
13#[derive(Clone)]
14pub struct Notification {
15    pub id: u64,
16    pub title: String,
17    pub message: String,
18    pub severity: NotificationSeverity,
19    pub timestamp: f64,
20    pub dismissed: bool,
21    pub persistent: bool,
22    pub duration_secs: f32,
23}
24
25#[allow(dead_code)]
26pub struct NotificationSystem {
27    pub notifications: Vec<Notification>,
28    pub next_id: u64,
29    pub current_time: f64,
30    pub max_notifications: usize,
31}
32
33#[allow(dead_code)]
34pub fn new_notification_system(max: usize) -> NotificationSystem {
35    NotificationSystem {
36        notifications: Vec::new(),
37        next_id: 1,
38        current_time: 0.0,
39        max_notifications: max,
40    }
41}
42
43#[allow(dead_code)]
44pub fn push_notification(
45    sys: &mut NotificationSystem,
46    title: &str,
47    msg: &str,
48    severity: NotificationSeverity,
49    duration: f32,
50) -> u64 {
51    let id = sys.next_id;
52    sys.next_id += 1;
53    let persistent = duration <= 0.0;
54    sys.notifications.push(Notification {
55        id,
56        title: title.to_string(),
57        message: msg.to_string(),
58        severity,
59        timestamp: sys.current_time,
60        dismissed: false,
61        persistent,
62        duration_secs: duration,
63    });
64    // Trim to max if needed — remove oldest dismissed first, then oldest overall
65    if sys.notifications.len() > sys.max_notifications {
66        let excess = sys.notifications.len() - sys.max_notifications;
67        let mut removed = 0;
68        sys.notifications.retain(|n| {
69            if removed < excess && n.dismissed {
70                removed += 1;
71                false
72            } else {
73                true
74            }
75        });
76        if sys.notifications.len() > sys.max_notifications {
77            let still_excess = sys.notifications.len() - sys.max_notifications;
78            sys.notifications.drain(0..still_excess);
79        }
80    }
81    id
82}
83
84#[allow(dead_code)]
85pub fn dismiss_notification(sys: &mut NotificationSystem, id: u64) -> bool {
86    if let Some(n) = sys.notifications.iter_mut().find(|n| n.id == id) {
87        if !n.dismissed {
88            n.dismissed = true;
89            return true;
90        }
91    }
92    false
93}
94
95#[allow(dead_code)]
96pub fn advance_notifications(sys: &mut NotificationSystem, dt: f64) {
97    sys.current_time += dt;
98    for n in &mut sys.notifications {
99        if !n.dismissed && !n.persistent {
100            let elapsed = sys.current_time - n.timestamp;
101            if elapsed >= n.duration_secs as f64 {
102                n.dismissed = true;
103            }
104        }
105    }
106    // Remove old dismissed notifications if over max
107    while sys.notifications.len() > sys.max_notifications {
108        let pos = sys.notifications.iter().position(|n| n.dismissed);
109        match pos {
110            Some(i) => {
111                sys.notifications.remove(i);
112            }
113            None => break,
114        }
115    }
116}
117
118#[allow(dead_code)]
119pub fn active_notifications(sys: &NotificationSystem) -> Vec<&Notification> {
120    sys.notifications.iter().filter(|n| !n.dismissed).collect()
121}
122
123#[allow(dead_code)]
124pub fn notification_by_id(sys: &NotificationSystem, id: u64) -> Option<&Notification> {
125    sys.notifications.iter().find(|n| n.id == id)
126}
127
128#[allow(dead_code)]
129pub fn notification_count(sys: &NotificationSystem) -> usize {
130    sys.notifications.len()
131}
132
133#[allow(dead_code)]
134pub fn active_count(sys: &NotificationSystem) -> usize {
135    sys.notifications.iter().filter(|n| !n.dismissed).count()
136}
137
138#[allow(dead_code)]
139pub fn notifications_by_severity(
140    sys: &NotificationSystem,
141    sev: NotificationSeverity,
142) -> Vec<&Notification> {
143    sys.notifications
144        .iter()
145        .filter(|n| n.severity == sev)
146        .collect()
147}
148
149#[allow(dead_code)]
150pub fn clear_all_notifications(sys: &mut NotificationSystem) {
151    sys.notifications.clear();
152}
153
154#[allow(dead_code)]
155pub fn has_errors(sys: &NotificationSystem) -> bool {
156    sys.notifications
157        .iter()
158        .any(|n| !n.dismissed && n.severity == NotificationSeverity::Error)
159}
160
161#[allow(dead_code)]
162pub fn push_info(sys: &mut NotificationSystem, title: &str, msg: &str) -> u64 {
163    push_notification(sys, title, msg, NotificationSeverity::Info, 5.0)
164}
165
166#[allow(dead_code)]
167pub fn push_error(sys: &mut NotificationSystem, title: &str, msg: &str) -> u64 {
168    push_notification(sys, title, msg, NotificationSeverity::Error, 10.0)
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_new_notification_system() {
177        let sys = new_notification_system(10);
178        assert_eq!(notification_count(&sys), 0);
179        assert_eq!(sys.max_notifications, 10);
180        assert_eq!(sys.next_id, 1);
181    }
182
183    #[test]
184    fn test_push_notification() {
185        let mut sys = new_notification_system(10);
186        let id = push_notification(&mut sys, "Test", "Message", NotificationSeverity::Info, 5.0);
187        assert_eq!(id, 1);
188        assert_eq!(notification_count(&sys), 1);
189    }
190
191    #[test]
192    fn test_dismiss_notification() {
193        let mut sys = new_notification_system(10);
194        let id = push_notification(&mut sys, "Test", "Msg", NotificationSeverity::Info, 5.0);
195        assert!(dismiss_notification(&mut sys, id));
196        assert!(!dismiss_notification(&mut sys, id));
197        assert_eq!(active_count(&sys), 0);
198    }
199
200    #[test]
201    fn test_dismiss_nonexistent() {
202        let mut sys = new_notification_system(10);
203        assert!(!dismiss_notification(&mut sys, 999));
204    }
205
206    #[test]
207    fn test_advance_auto_dismisses() {
208        let mut sys = new_notification_system(10);
209        push_notification(&mut sys, "T", "M", NotificationSeverity::Info, 3.0);
210        assert_eq!(active_count(&sys), 1);
211        advance_notifications(&mut sys, 4.0);
212        assert_eq!(active_count(&sys), 0);
213    }
214
215    #[test]
216    fn test_advance_persistent_not_dismissed() {
217        let mut sys = new_notification_system(10);
218        push_notification(&mut sys, "T", "M", NotificationSeverity::Warning, 0.0);
219        advance_notifications(&mut sys, 100.0);
220        assert_eq!(active_count(&sys), 1);
221    }
222
223    #[test]
224    fn test_active_notifications() {
225        let mut sys = new_notification_system(10);
226        let id1 = push_notification(&mut sys, "A", "M1", NotificationSeverity::Info, 5.0);
227        push_notification(&mut sys, "B", "M2", NotificationSeverity::Warning, 5.0);
228        dismiss_notification(&mut sys, id1);
229        let active = active_notifications(&sys);
230        assert_eq!(active.len(), 1);
231        assert_eq!(active[0].title, "B");
232    }
233
234    #[test]
235    fn test_notification_by_id() {
236        let mut sys = new_notification_system(10);
237        let id = push_notification(&mut sys, "Title", "Msg", NotificationSeverity::Info, 5.0);
238        let n = notification_by_id(&sys, id);
239        assert!(n.is_some());
240        assert_eq!(n.expect("should succeed").title, "Title");
241        assert!(notification_by_id(&sys, 999).is_none());
242    }
243
244    #[test]
245    fn test_notifications_by_severity() {
246        let mut sys = new_notification_system(10);
247        push_notification(&mut sys, "E1", "M", NotificationSeverity::Error, 5.0);
248        push_notification(&mut sys, "E2", "M", NotificationSeverity::Error, 5.0);
249        push_notification(&mut sys, "I1", "M", NotificationSeverity::Info, 5.0);
250        let errors = notifications_by_severity(&sys, NotificationSeverity::Error);
251        assert_eq!(errors.len(), 2);
252    }
253
254    #[test]
255    fn test_has_errors_true() {
256        let mut sys = new_notification_system(10);
257        push_error(&mut sys, "Error", "Something broke");
258        assert!(has_errors(&sys));
259    }
260
261    #[test]
262    fn test_has_errors_false() {
263        let mut sys = new_notification_system(10);
264        push_info(&mut sys, "Info", "All good");
265        assert!(!has_errors(&sys));
266    }
267
268    #[test]
269    fn test_has_errors_dismissed() {
270        let mut sys = new_notification_system(10);
271        let id = push_error(&mut sys, "Error", "Bad");
272        dismiss_notification(&mut sys, id);
273        assert!(!has_errors(&sys));
274    }
275
276    #[test]
277    fn test_clear_all_notifications() {
278        let mut sys = new_notification_system(10);
279        push_info(&mut sys, "A", "M1");
280        push_info(&mut sys, "B", "M2");
281        clear_all_notifications(&mut sys);
282        assert_eq!(notification_count(&sys), 0);
283    }
284
285    #[test]
286    fn test_push_info() {
287        let mut sys = new_notification_system(10);
288        let id = push_info(&mut sys, "Hello", "World");
289        let n = notification_by_id(&sys, id).expect("should succeed");
290        assert_eq!(n.severity, NotificationSeverity::Info);
291        assert!((n.duration_secs - 5.0).abs() < 1e-6);
292    }
293
294    #[test]
295    fn test_push_error() {
296        let mut sys = new_notification_system(10);
297        let id = push_error(&mut sys, "Oops", "Failed");
298        let n = notification_by_id(&sys, id).expect("should succeed");
299        assert_eq!(n.severity, NotificationSeverity::Error);
300        assert!((n.duration_secs - 10.0).abs() < 1e-6);
301    }
302
303    #[test]
304    fn test_notification_count() {
305        let mut sys = new_notification_system(10);
306        assert_eq!(notification_count(&sys), 0);
307        push_info(&mut sys, "A", "M");
308        push_info(&mut sys, "B", "M");
309        assert_eq!(notification_count(&sys), 2);
310    }
311
312    #[test]
313    fn test_active_count() {
314        let mut sys = new_notification_system(10);
315        let id = push_info(&mut sys, "A", "M");
316        push_info(&mut sys, "B", "M");
317        assert_eq!(active_count(&sys), 2);
318        dismiss_notification(&mut sys, id);
319        assert_eq!(active_count(&sys), 1);
320    }
321}