- Add reqwest 0.12 (json + stream features) — was missing from Cargo.toml causing chat_stream to fail to compile - Add Android/IpadOs arms to SourceOs match in chat command - Add tauri::Manager import to notify.rs (needed for .state()) - Replace deprecated menu_on_left_click with show_menu_on_left_click - Remove desktopTemplate from tauri.conf.json (not in tauri-utils 2.9.2 schema) - Commit Cargo.lock so cross-machine builds pin identical crate versions All 42 unit tests pass on Linux Mint 22.3 (Muninn).
95 lines
2.6 KiB
Rust
95 lines
2.6 KiB
Rust
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Mutex;
|
|
use tauri::{AppHandle, Emitter, Manager, Runtime};
|
|
use tauri_plugin_notification::NotificationExt;
|
|
|
|
use crate::commands::AppState;
|
|
use crate::config::NotificationLevel;
|
|
use crate::patterns::MatchedEvent;
|
|
|
|
static PENDING: Mutex<Vec<MatchedEvent>> = Mutex::new(vec![]);
|
|
// True while ChatPanel is mounted and listening for live events.
|
|
// When true, dispatch skips PENDING so events are not shown twice on re-open.
|
|
static PANEL_OPEN: AtomicBool = AtomicBool::new(false);
|
|
|
|
pub fn set_panel_open(open: bool) {
|
|
PANEL_OPEN.store(open, Ordering::Relaxed);
|
|
}
|
|
|
|
pub fn dispatch<R: Runtime>(app: &AppHandle<R>, event: MatchedEvent) {
|
|
let level = app
|
|
.state::<AppState>()
|
|
.config
|
|
.lock()
|
|
.map(|c| c.display.notification_level.clone())
|
|
.unwrap_or_default();
|
|
|
|
if !PANEL_OPEN.load(Ordering::Relaxed) {
|
|
match PENDING.lock() {
|
|
Ok(mut pending) => pending.push(event.clone()),
|
|
Err(e) => log::warn!("notify: pending queue lock poisoned — event dropped: {e}"),
|
|
}
|
|
}
|
|
|
|
match level {
|
|
NotificationLevel::Off => {}
|
|
NotificationLevel::BadgeOnly => {
|
|
crate::tray::badge_on(app);
|
|
}
|
|
NotificationLevel::BadgeAndToast => {
|
|
crate::tray::badge_on(app);
|
|
send_toast(app, &event);
|
|
}
|
|
}
|
|
|
|
let _ = app.emit("robin:event", &event);
|
|
}
|
|
|
|
fn send_toast<R: Runtime>(app: &AppHandle<R>, event: &MatchedEvent) {
|
|
let _ = app
|
|
.notification()
|
|
.builder()
|
|
.title(&event.title)
|
|
.body(&event.body)
|
|
.show();
|
|
}
|
|
|
|
pub fn take_pending() -> Vec<MatchedEvent> {
|
|
PENDING
|
|
.lock()
|
|
.map(|mut v| std::mem::take(&mut *v))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn make_event(id: &str) -> MatchedEvent {
|
|
MatchedEvent {
|
|
pattern_id: id.into(),
|
|
title: "Title".into(),
|
|
body: "Body".into(),
|
|
severity: "warn".into(),
|
|
timestamp: 0,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn take_pending_drains_queue() {
|
|
// Ensure clean state — drain any events from other tests sharing the static
|
|
let _ = take_pending();
|
|
|
|
if let Ok(mut q) = PENDING.lock() {
|
|
q.push(make_event("a"));
|
|
q.push(make_event("b"));
|
|
}
|
|
|
|
let first = take_pending();
|
|
assert_eq!(first.len(), 2);
|
|
assert_eq!(first[0].pattern_id, "a");
|
|
|
|
let second = take_pending();
|
|
assert!(second.is_empty(), "queue must be empty after drain");
|
|
}
|
|
}
|