Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pickledb = { version = "0.5.1", features = ["bincode"] }
# Directories
dirs = "6.0.0"

# Notifications
notify-rust = "4.18.0"
# WebSockets
tokio-tungstenite = { version = "0.30.0", features = ["native-tls"] }
futures-util = "0.3.31"
Expand Down
30 changes: 30 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ 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 log::debug;
use ratatui::crossterm::event::{self, Event};

use crate::notification::NotifyHandler;

pub const LOG_FILE: &str = "logs";

pub type Result<T> = anyhow::Result<T>;
Expand Down Expand Up @@ -54,6 +58,32 @@ async fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {

app.authenticate_ws(&app.api_client.clone_token()).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))?;

Expand Down
79 changes: 79 additions & 0 deletions src/notification.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf> {
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<PathBuf>,
}

impl NotifyHandler {
pub async fn new() -> Result<Self> {
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:?}");
}
}
});
}
}
3 changes: 1 addition & 2 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
mod error;
mod input_token;
mod render;
mod server_list;
mod validating_token;

mod render;

pub use render::render;