From 76f2ef6acab1e5c84700b1d4da83ee9f14830945 Mon Sep 17 00:00:00 2001 From: YetAnotherMechanicusEnjoyer Date: Wed, 29 Jul 2026 23:23:29 +0200 Subject: [PATCH] feat: add NotifyHandler with notify_rust crate --- Cargo.toml | 8 ++++- src/main.rs | 30 +++++++++++++++++ src/notification.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++ src/ui/mod.rs | 3 +- 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/notification.rs diff --git a/Cargo.toml b/Cargo.toml index ab224ae..9dc6055 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,15 @@ pickledb = { version = "0.5.1", features = ["bincode"] } # Directories dirs = "6.0.0" +# Notifications +notify-rust = "4.18.0" + keyring-lib = { version = "1.0.3", features = ["tokio"] } ratatui = "0.30.2" -reqwest = { version = "0.13.4", default-features = false, features = ["json", "rustls"] } +reqwest = { version = "0.13.4", default-features = false, features = [ + "json", + "rustls", +] } serde = { version = "1", features = ["derive"] } tokio = { version = "1.52.3", features = ["full"] } serde_json = "1.0.150" diff --git a/src/main.rs b/src/main.rs index af3f991..ab8f2ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,17 @@ mod app; mod cache; mod error; mod input; +mod notification; mod ui; use std::{fs, path::PathBuf}; use app::App; +use notify_rust::{CloseReason, NotificationResponse}; use ratatui::crossterm::event::{self, Event}; +use crate::notification::NotifyHandler; + pub const LOG_FILE: &str = "logs"; pub type Result = anyhow::Result; @@ -48,6 +52,32 @@ async fn main() -> anyhow::Result<(), Box> { let mut app = App::new().await?; + /* This is an example, for now we have no use for notifications */ + { + let n = NotifyHandler::new().await?; + let icon = n.clone_icon_path(); + + n.send_notification( + "This is a title".to_string(), + "This is a body".to_string(), + icon, + vec![ + ("reply".to_string(), "Reply".to_string()), + ("read".to_string(), "Mark as read".to_string()), + ], + |response: &NotificationResponse| match response { + NotificationResponse::Default => log::info!("body clicked"), + NotificationResponse::Action(key) => log::info!("button {key:?} clicked"), + NotificationResponse::Reply(text) => log::info!("user replied: {text}"), + NotificationResponse::Closed(CloseReason::Dismissed) => { + log::info!("dismissed by the user") + } + NotificationResponse::Closed(reason) => log::info!("closed: {reason:?}"), + }, + ) + .await; + } + loop { terminal.draw(|f| ui::render(f, &app))?; diff --git a/src/notification.rs b/src/notification.rs new file mode 100644 index 0000000..0d1fcd0 --- /dev/null +++ b/src/notification.rs @@ -0,0 +1,79 @@ +use log::{error, warn}; +use notify_rust::{Notification, ResponseHandler, Timeout}; +use std::{fs, path::PathBuf, thread}; + +use crate::Result; + +pub const ICON_FILE: &str = "icon"; // since idk which file extention we'll be using, I'm leaving it without +pub const NOTIFICATION_TIMEOUT: u32 = 10_000; + +fn get_icon_path() -> Option { + let mut icon_path = dirs::data_dir()?; + icon_path.push(env!("CARGO_PKG_NAME")); + + if let Err(e) = fs::create_dir_all(&icon_path) { + error!("Error creating data directory: {e:?}"); + return None; + }; + + icon_path.push(ICON_FILE); + Some(icon_path) +} + +pub struct NotifyHandler { + icon_path: Option, +} + +impl NotifyHandler { + pub async fn new() -> Result { + let icon_path = get_icon_path(); + if icon_path.is_none() { + warn!("Failed to find icon path!"); + } + + Ok(Self { icon_path }) + } + + pub fn clone_icon_path(&self) -> String { + self.icon_path + .clone() + .unwrap_or_default() + .to_string_lossy() + .to_string() + } + + pub async fn send_notification( + &self, + title: String, + body: String, + icon: String, + actions: Vec<(String, String)>, + response: impl ResponseHandler + Send + 'static, + ) { + thread::spawn(move || { + let mut notification = Notification::new() + .appname(env!("CARGO_PKG_NAME")) + .summary(&title) + .body(&body) + .timeout(Timeout::Milliseconds(NOTIFICATION_TIMEOUT)) + .icon(&icon) + .clone(); + + for action in actions { + notification.action(&action.0, &action.1); + } + + let res = notification.show(); + match res { + Ok(handle) => { + if let Err(e) = handle.wait_for_response(response) { + error!("Error fetching notification response: {e:?}"); + }; + } + Err(e) => { + error!("Error sending notification: {e:?}"); + } + } + }); + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index db8968f..4904799 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,8 +1,7 @@ mod error; mod input_token; +mod render; mod server_list; mod validating_token; -mod render; - pub use render::render;