Clear desktop notification when message is read (#427)

Co-authored-by: Ulyssa <git@ulyssa.dev>
This commit is contained in:
vaw 2025-07-23 05:19:23 +00:00 committed by GitHub
parent 0ff8828a1c
commit e9cdb3371a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 62 additions and 10 deletions

View file

@ -92,6 +92,7 @@ use modalkit::{
use crate::config::ImagePreviewProtocolValues; use crate::config::ImagePreviewProtocolValues;
use crate::message::ImageStatus; use crate::message::ImageStatus;
use crate::notifications::NotificationHandle;
use crate::preview::{source_from_event, spawn_insert_preview}; use crate::preview::{source_from_event, spawn_insert_preview};
use crate::{ use crate::{
message::{Message, MessageEvent, MessageKey, MessageTimeStamp, Messages}, message::{Message, MessageEvent, MessageKey, MessageTimeStamp, Messages},
@ -1558,6 +1559,9 @@ pub struct ChatStore {
/// Collator for locale-aware text sorting. /// Collator for locale-aware text sorting.
pub collator: feruca::Collator, pub collator: feruca::Collator,
/// Notifications that should be dismissed when the user opens the room.
pub open_notifications: HashMap<OwnedRoomId, Vec<NotificationHandle>>,
} }
impl ChatStore { impl ChatStore {
@ -1582,6 +1586,7 @@ impl ChatStore {
draw_curr: None, draw_curr: None,
ring_bell: false, ring_bell: false,
focused: true, focused: true,
open_notifications: Default::default(),
} }
} }

View file

@ -561,6 +561,9 @@ impl Application {
IambAction::ClearUnreads => { IambAction::ClearUnreads => {
let user_id = &store.application.settings.profile.user_id; let user_id = &store.application.settings.profile.user_id;
// Clear any notifications we displayed:
store.application.open_notifications.clear();
for room_id in store.application.sync_info.chats() { for room_id in store.application.sync_info.chats() {
if let Some(room) = store.application.rooms.get_mut(room_id) { if let Some(room) = store.application.rooms.get_mut(room_id) {
room.fully_read(user_id); room.fully_read(user_id);

View file

@ -8,6 +8,7 @@ use matrix_sdk::{
events::{room::message::MessageType, AnyMessageLikeEventContent, AnySyncTimelineEvent}, events::{room::message::MessageType, AnyMessageLikeEventContent, AnySyncTimelineEvent},
serde::Raw, serde::Raw,
MilliSecondsSinceUnixEpoch, MilliSecondsSinceUnixEpoch,
OwnedRoomId,
RoomId, RoomId,
}, },
Client, Client,
@ -24,6 +25,21 @@ const IAMB_XDG_NAME: &str = match option_env!("IAMB_XDG_NAME") {
Some(iamb) => iamb, Some(iamb) => iamb,
}; };
/// Handle for an open notification that should be closed when the user views it.
pub struct NotificationHandle(
#[cfg(all(feature = "desktop", unix, not(target_os = "macos")))]
Option<notify_rust::NotificationHandle>,
);
impl Drop for NotificationHandle {
fn drop(&mut self) {
#[cfg(all(feature = "desktop", unix, not(target_os = "macos")))]
if let Some(handle) = self.0.take() {
handle.close();
}
}
}
pub async fn register_notifications( pub async fn register_notifications(
client: &Client, client: &Client,
settings: &ApplicationSettings, settings: &ApplicationSettings,
@ -54,6 +70,7 @@ pub async fn register_notifications(
return; return;
} }
let room_id = room.room_id().to_owned();
match notification.event { match notification.event {
RawAnySyncOrStrippedTimelineEvent::Sync(e) => { RawAnySyncOrStrippedTimelineEvent::Sync(e) => {
match parse_full_notification(e, room, show_message).await { match parse_full_notification(e, room, show_message).await {
@ -66,7 +83,13 @@ pub async fn register_notifications(
return; return;
} }
send_notification(&notify_via, &store, &summary, body.as_deref()) send_notification(
&notify_via,
&summary,
body.as_deref(),
room_id,
&store,
)
.await; .await;
}, },
Err(err) => { Err(err) => {
@ -86,13 +109,14 @@ pub async fn register_notifications(
async fn send_notification( async fn send_notification(
via: &NotifyVia, via: &NotifyVia,
store: &AsyncProgramStore,
summary: &str, summary: &str,
body: Option<&str>, body: Option<&str>,
room_id: OwnedRoomId,
store: &AsyncProgramStore,
) { ) {
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
if via.desktop { if via.desktop {
send_notification_desktop(summary, body); send_notification_desktop(summary, body, room_id, store).await;
} }
#[cfg(not(feature = "desktop"))] #[cfg(not(feature = "desktop"))]
{ {
@ -110,7 +134,12 @@ async fn send_notification_bell(store: &AsyncProgramStore) {
} }
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
fn send_notification_desktop(summary: &str, body: Option<&str>) { async fn send_notification_desktop(
summary: &str,
body: Option<&str>,
room_id: OwnedRoomId,
_store: &AsyncProgramStore,
) {
let mut desktop_notification = notify_rust::Notification::new(); let mut desktop_notification = notify_rust::Notification::new();
desktop_notification desktop_notification
.summary(summary) .summary(summary)
@ -125,8 +154,19 @@ fn send_notification_desktop(summary: &str, body: Option<&str>) {
desktop_notification.body(body); desktop_notification.body(body);
} }
if let Err(err) = desktop_notification.show() { match desktop_notification.show() {
tracing::error!("Failed to send notification: {err}") Err(err) => tracing::error!("Failed to send notification: {err}"),
Ok(handle) => {
#[cfg(all(unix, not(target_os = "macos")))]
_store
.lock()
.await
.application
.open_notifications
.entry(room_id)
.or_default()
.push(NotificationHandle(Some(handle)));
},
} }
} }

View file

@ -502,19 +502,23 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) {
loop { loop {
interval.tick().await; interval.tick().await;
let locked = store.lock().await; let mut locked = store.lock().await;
let user_id = &locked.application.settings.profile.user_id; let ChatStore { settings, open_notifications, rooms, .. } = &mut locked.application;
let user_id = &settings.profile.user_id;
let mut updates = Vec::new(); let mut updates = Vec::new();
for room in client.joined_rooms() { for room in client.joined_rooms() {
let room_id = room.room_id(); let room_id = room.room_id();
let Some(info) = locked.application.rooms.get(&room_id) else { let Some(info) = rooms.get(room_id) else {
continue; continue;
}; };
let changed = info.receipts(user_id).filter_map(|(thread, new_receipt)| { let changed = info.receipts(user_id).filter_map(|(thread, new_receipt)| {
let old_receipt = sent.get(room_id).and_then(|ts| ts.get(thread)); let old_receipt = sent.get(room_id).and_then(|ts| ts.get(thread));
let changed = Some(new_receipt) != old_receipt; let changed = Some(new_receipt) != old_receipt;
if changed {
open_notifications.remove(room_id);
}
changed.then(|| (room_id.to_owned(), thread.to_owned(), new_receipt.to_owned())) changed.then(|| (room_id.to_owned(), thread.to_owned(), new_receipt.to_owned()))
}); });