init
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
//! Notif widget: notification counters, desktop notifications and snackbars.
|
||||
//! `notifs_key` tracks what the user is currently looking at (`chat|jid`…).
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::P;
|
||||
use crate::{app::Ctx, db, rpc};
|
||||
|
||||
pub struct Notification<'a> {
|
||||
pub key: String,
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
pub url: String,
|
||||
pub picture: Option<String>,
|
||||
pub time: i64,
|
||||
pub actions: Value,
|
||||
pub data: Value,
|
||||
pub rpc_call: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// Number of conversations with unread messages
|
||||
pub async fn unreads(ctx: &Ctx) -> i64 {
|
||||
let me = ctx.jid();
|
||||
let rooms: Vec<String> = ctx
|
||||
.st(|st| st.conferences.values().filter(|c| c.notify > 0).map(|c| c.jid.clone()).collect())
|
||||
.unwrap_or_default();
|
||||
db::unreads_total(ctx.db(), &me, &rooms).await.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub async fn append(ctx: &Ctx, n: Notification<'_>) {
|
||||
let picture = n.picture.clone().unwrap_or_else(|| format!("{}theme/img/app/128.png", ctx.base_uri()));
|
||||
let mut data = n.data.clone();
|
||||
data["url"] = json!(n.url);
|
||||
|
||||
crate::push::notify(ctx, &n.key, &n.title, &n.body, &picture, &data).await;
|
||||
|
||||
rpc!(
|
||||
ctx,
|
||||
"Notif.desktop",
|
||||
n.key.clone(),
|
||||
chrono::Utc::now().timestamp(),
|
||||
n.title.clone(),
|
||||
n.body.clone(),
|
||||
picture.clone(),
|
||||
n.actions.clone(),
|
||||
data
|
||||
);
|
||||
|
||||
let first = n.key.split('|').next().unwrap_or("").to_string();
|
||||
let current = ctx.st(|st| st.notifs_key.clone()).flatten();
|
||||
|
||||
let (first_count, key_count) = ctx
|
||||
.st(|st| {
|
||||
let c = st.notifs.shift_remove(&first).unwrap_or(0) + 1;
|
||||
st.notifs.insert(first.clone(), c);
|
||||
let mut k = 0;
|
||||
if first != n.key {
|
||||
k = st.notifs.shift_remove(&n.key).unwrap_or(0) + 1;
|
||||
st.notifs.insert(n.key.clone(), k);
|
||||
}
|
||||
(c, k)
|
||||
})
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
// Do not notify what is currently displayed
|
||||
if current.as_deref() == Some(n.key.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if first == "chat" {
|
||||
rpc!(ctx, "Notif.counter", first.clone(), unreads(ctx).await);
|
||||
} else {
|
||||
rpc!(ctx, "Notif.counter", first.clone(), first_count);
|
||||
}
|
||||
if let Some(call) = n.rpc_call {
|
||||
rpc!(ctx, call);
|
||||
}
|
||||
if first != n.key {
|
||||
rpc!(ctx, "Notif.counter", n.key.clone(), key_count);
|
||||
}
|
||||
|
||||
if !n.title.is_empty() {
|
||||
let html = ctx.render(
|
||||
"Notif/_notif.html",
|
||||
json!({ "title": n.title, "body": n.body, "picture": picture, "url": n.url }),
|
||||
);
|
||||
rpc!(ctx, "Notif.snackbar", html, n.time);
|
||||
}
|
||||
|
||||
super::presence::refresh_shortcuts(ctx).await;
|
||||
}
|
||||
|
||||
pub async fn clear(ctx: &Ctx, key: &str) {
|
||||
let removed = ctx
|
||||
.st(|st| {
|
||||
let counter = st.notifs.shift_remove(key)?;
|
||||
let first = key.split('|').next().unwrap_or("").to_string();
|
||||
let mut first_left = None;
|
||||
if let Some(v) = st.notifs.get_mut(&first) {
|
||||
*v -= counter;
|
||||
first_left = Some((first.clone(), (*v).max(0)));
|
||||
if *v <= 0 {
|
||||
st.notifs.shift_remove(&first);
|
||||
}
|
||||
}
|
||||
Some(first_left)
|
||||
})
|
||||
.flatten();
|
||||
|
||||
if let Some(first_left) = removed {
|
||||
rpc!(ctx, "Notif.counter", key, 0);
|
||||
rpc!(ctx, "Notif.clear", key);
|
||||
if let Some((first, left)) = first_left {
|
||||
rpc!(ctx, "Notif.counter", first, left);
|
||||
}
|
||||
if let Some(jid) = key.strip_prefix("chat|") {
|
||||
rpc!(ctx, "Shortcuts.clear", jid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(ctx: &Ctx) -> Option<String> {
|
||||
ctx.st(|st| st.notifs_key.clone()).flatten()
|
||||
}
|
||||
|
||||
/// Is the conversation currently displayed and focused?
|
||||
pub fn is_current(ctx: &Ctx, jid: &str) -> bool {
|
||||
current(ctx).is_some_and(|k| k == format!("chat|{jid}"))
|
||||
}
|
||||
|
||||
pub async fn call(ctx: &Ctx, func: &str, p: &P<'_>) -> bool {
|
||||
match func {
|
||||
"ajaxClear" => clear(ctx, &p.str(0)).await,
|
||||
"ajaxGet" => {
|
||||
let chat = p.v(0).is_none() || p.b(0);
|
||||
let mut notifs: serde_json::Map<String, Value> = ctx
|
||||
.st(|st| st.notifs.iter().map(|(k, v)| (k.clone(), json!(v))).collect())
|
||||
.unwrap_or_default();
|
||||
if chat {
|
||||
notifs.insert("chat".into(), json!(unreads(ctx).await));
|
||||
}
|
||||
rpc!(ctx, "Notif.refresh", Value::Object(notifs));
|
||||
}
|
||||
"ajaxCurrent" => {
|
||||
let key = p.str(0);
|
||||
if key.contains('|') {
|
||||
clear(ctx, &key).await;
|
||||
}
|
||||
let was_blurred = ctx.st(|st| st.notifs_key.as_deref() == Some("blurred")).unwrap_or(false);
|
||||
ctx.st(|st| st.notifs_key = Some(key.clone()));
|
||||
if was_blurred {
|
||||
if let Some(jid) = key.strip_prefix("chat|") {
|
||||
super::chat::on_focus_back(ctx, jid).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
"ajaxRegisterPushSubscrition" => {
|
||||
let (endpoint, auth, p256dh) = (p.str(0), p.str(1), p.str(2));
|
||||
if endpoint.starts_with("https://") {
|
||||
let _ = db::push_subscription_save(ctx.db(), &ctx.jid(), &endpoint, &p256dh, &auth).await;
|
||||
}
|
||||
}
|
||||
"ajaxHttpTouchPushSubscription" => {
|
||||
let _ = db::push_subscription_touch(ctx.db(), &p.str(0)).await;
|
||||
}
|
||||
"ajaxHttpRequest" => {
|
||||
super::dialog(ctx, &ctx.render("Notif/_notif_request.html", json!({})), false, false);
|
||||
}
|
||||
"ajaxRequestGranted" => {
|
||||
rpc!(
|
||||
ctx,
|
||||
"Notif.desktop",
|
||||
chrono::Utc::now().timestamp(),
|
||||
ctx.t("notification.request_title", &[]),
|
||||
ctx.t("notification.request_granted", &[]),
|
||||
Value::Null,
|
||||
Value::Null,
|
||||
Value::Null,
|
||||
Value::Null,
|
||||
Value::Null,
|
||||
true
|
||||
);
|
||||
}
|
||||
"ajaxRequestDenied" => ctx.toast(&ctx.t("notification.request_denied", &[])),
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn on_chat_counter(ctx: &Ctx) {
|
||||
let n = unreads(ctx).await;
|
||||
rpc!(ctx, "Notif.counter", "chat", n);
|
||||
rpc!(ctx, "MovimUtils.setDataItem", "#chatcounter", "counter", n);
|
||||
rpc!(ctx, "MovimUtils.setDataItem", "#chatheadercounter", "counter", n);
|
||||
rpc!(ctx, "BottomNavigation.setChatNotification", json!({ "chat": n }));
|
||||
}
|
||||
Reference in New Issue
Block a user