notifications-page: Add global rooms setting

This commit is contained in:
Kévin Commaille 2023-12-16 12:48:45 +01:00
parent 59262cc141
commit cd708d637d
No known key found for this signature in database
GPG Key ID: 29A48C1F03620416
5 changed files with 283 additions and 8 deletions

View File

@ -10,7 +10,7 @@ mod verification;
pub use self::{
avatar_data::{AvatarData, AvatarImage, AvatarUriSource},
notifications::{Notifications, NotificationsSettings},
notifications::{Notifications, NotificationsGlobalSetting, NotificationsSettings},
room::{
Event, EventKey, HighlightFlags, Member, MemberList, MemberRole, Membership, MessageState,
PowerLevel, ReactionGroup, ReactionList, Room, RoomType, Timeline, TimelineItem,

View File

@ -12,7 +12,7 @@ use tracing::{debug, error, warn};
mod notifications_settings;
pub use self::notifications_settings::NotificationsSettings;
pub use self::notifications_settings::{NotificationsGlobalSetting, NotificationsSettings};
use super::{Room, Session};
use crate::{
application::AppShowRoomPayload, prelude::*, spawn, spawn_tokio, utils::matrix::get_event_body,

View File

@ -1,7 +1,9 @@
use futures_util::StreamExt;
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::{
notification_settings::NotificationSettings as MatrixNotificationSettings,
notification_settings::{
IsEncrypted, NotificationSettings as MatrixNotificationSettings, RoomNotificationMode,
},
NotificationSettingsError,
};
use ruma::push::{PredefinedOverrideRuleId, RuleKind};
@ -13,6 +15,23 @@ use crate::{
spawn, spawn_tokio,
};
/// The possible values for the global notifications setting.
#[derive(
Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, strum::Display, strum::EnumString,
)]
#[repr(u32)]
#[enum_type(name = "NotificationsGlobalSetting")]
#[strum(serialize_all = "kebab-case")]
pub enum NotificationsGlobalSetting {
/// Every message in every room.
#[default]
All = 0,
/// Every message in 1-to-1 rooms, and mentions and keywords in every room.
DirectAndMentions = 1,
/// Only mentions and keywords in every room.
MentionsOnly = 2,
}
mod imp {
use std::cell::{Cell, RefCell};
@ -32,6 +51,9 @@ mod imp {
/// Whether notifications are enabled for this session.
#[property(get, set = Self::set_session_enabled, explicit_notify)]
pub session_enabled: Cell<bool>,
/// The global setting about which messages trigger notifications.
#[property(get, builder(NotificationsGlobalSetting::default()))]
pub global_setting: Cell<NotificationsGlobalSetting>,
}
#[glib::object_subclass]
@ -181,6 +203,14 @@ impl NotificationsSettings {
}
};
self.set_account_enabled_inner(account_enabled);
if default_rooms_notifications_is_all(api.clone(), false).await {
self.set_global_setting_inner(NotificationsGlobalSetting::All);
} else if default_rooms_notifications_is_all(api.clone(), true).await {
self.set_global_setting_inner(NotificationsGlobalSetting::DirectAndMentions);
} else {
self.set_global_setting_inner(NotificationsGlobalSetting::MentionsOnly);
}
}
/// Set whether notifications are enabled for this session.
@ -223,6 +253,46 @@ impl NotificationsSettings {
self.imp().account_enabled.set(enabled);
self.notify_account_enabled();
}
/// Set the global setting about which messages trigger notifications.
pub async fn set_global_setting(
&self,
setting: NotificationsGlobalSetting,
) -> Result<(), NotificationSettingsError> {
let Some(api) = self.api() else {
error!("Cannot update notifications settings when API is not initialized");
return Err(NotificationSettingsError::UnableToUpdatePushRule);
};
let (group_all, one_to_one_all) = match setting {
NotificationsGlobalSetting::All => (true, true),
NotificationsGlobalSetting::DirectAndMentions => (false, true),
NotificationsGlobalSetting::MentionsOnly => (false, false),
};
if let Err(error) = set_default_rooms_notifications_all(api.clone(), false, group_all).await
{
error!("Failed to change global group chats notifications setting: {error}");
return Err(error);
}
if let Err(error) = set_default_rooms_notifications_all(api, true, one_to_one_all).await {
error!("Failed to change global 1-to-1 chats notifications setting: {error}");
return Err(error);
}
self.set_global_setting_inner(setting);
Ok(())
}
fn set_global_setting_inner(&self, setting: NotificationsGlobalSetting) {
if self.global_setting() == setting {
return;
}
self.imp().global_setting.set(setting);
self.notify_global_setting();
}
}
impl Default for NotificationsSettings {
@ -230,3 +300,36 @@ impl Default for NotificationsSettings {
Self::new()
}
}
async fn default_rooms_notifications_is_all(
api: MatrixNotificationSettings,
is_one_to_one: bool,
) -> bool {
let mode = spawn_tokio!(async move {
api.get_default_room_notification_mode(IsEncrypted::No, is_one_to_one.into())
.await
})
.await
.unwrap();
mode == RoomNotificationMode::AllMessages
}
async fn set_default_rooms_notifications_all(
api: MatrixNotificationSettings,
is_one_to_one: bool,
all: bool,
) -> Result<(), NotificationSettingsError> {
let mode = if all {
RoomNotificationMode::AllMessages
} else {
RoomNotificationMode::MentionsAndKeywordsOnly
};
spawn_tokio!(async move {
api.set_default_room_notification_mode(IsEncrypted::No, is_one_to_one.into(), mode)
.await
})
.await
.unwrap()
}

View File

@ -1,14 +1,17 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, glib::clone, CompositeTemplate};
use tracing::error;
use crate::{
components::Spinner, session::model::NotificationsSettings, spawn, toast,
components::{LoadingBin, Spinner},
session::model::{NotificationsGlobalSetting, NotificationsSettings},
spawn, toast,
utils::BoundObjectWeakRef,
};
mod imp {
use std::cell::Cell;
use std::{cell::Cell, marker::PhantomData};
use glib::subclass::InitializingObject;
@ -24,12 +27,32 @@ mod imp {
pub account_switch: TemplateChild<gtk::Switch>,
#[template_child]
pub session_row: TemplateChild<adw::SwitchRow>,
#[template_child]
pub global: TemplateChild<adw::PreferencesGroup>,
#[template_child]
pub global_all_bin: TemplateChild<LoadingBin>,
#[template_child]
pub global_all_radio: TemplateChild<gtk::CheckButton>,
#[template_child]
pub global_direct_bin: TemplateChild<LoadingBin>,
#[template_child]
pub global_direct_radio: TemplateChild<gtk::CheckButton>,
#[template_child]
pub global_mentions_bin: TemplateChild<LoadingBin>,
#[template_child]
pub global_mentions_radio: TemplateChild<gtk::CheckButton>,
/// The notifications settings of the current session.
#[property(get, set = Self::set_notifications_settings, explicit_notify)]
pub notifications_settings: BoundObjectWeakRef<NotificationsSettings>,
/// Whether the account section is busy.
#[property(get)]
pub account_loading: Cell<bool>,
/// Whether the global section is busy.
#[property(get)]
pub global_loading: Cell<bool>,
/// The global notifications setting, as a string.
#[property(get = Self::global_setting, set = Self::set_global_setting)]
pub global_setting: PhantomData<String>,
}
#[glib::object_subclass]
@ -43,6 +66,8 @@ mod imp {
Self::bind_template(klass);
Self::Type::bind_template_callbacks(klass);
klass.install_property_action("notifications.set-global-default", "global-setting");
}
fn instance_init(obj: &InitializingObject<Self>) {
@ -78,17 +103,46 @@ mod imp {
settings.connect_session_enabled_notify(clone!(@weak obj => move |_| {
obj.update_session();
}));
let global_setting_handler =
settings.connect_global_setting_notify(clone!(@weak obj => move |_| {
obj.update_global();
}));
self.notifications_settings.set(
settings,
vec![account_enabled_handler, session_enabled_handler],
vec![
account_enabled_handler,
session_enabled_handler,
global_setting_handler,
],
);
}
obj.update_account();
obj.update_session();
obj.notify_notifications_settings();
}
/// The global notifications setting, as a string.
fn global_setting(&self) -> String {
let Some(settings) = self.notifications_settings.obj() else {
return String::new();
};
settings.global_setting().to_string()
}
/// Set the global notifications setting, as a string.
fn set_global_setting(&self, default: String) {
let default = match default.parse::<NotificationsGlobalSetting>() {
Ok(default) => default,
Err(_) => {
error!("Invalid value to set global default notifications setting: {default}");
return;
}
};
self.obj().global_setting_changed(default);
}
}
}
@ -116,7 +170,7 @@ impl NotificationsPage {
imp.account_switch.set_active(settings.account_enabled());
imp.account_switch.set_sensitive(!self.account_loading());
// Other sessions will be disabled or not.
// Other sections will be disabled or not.
self.update_session();
}
@ -129,6 +183,24 @@ impl NotificationsPage {
imp.session_row.set_active(settings.session_enabled());
imp.session_row.set_sensitive(settings.account_enabled());
// Other sections will be disabled or not.
self.update_global();
}
/// Update the section about global.
fn update_global(&self) {
let Some(settings) = self.notifications_settings() else {
return;
};
let imp = self.imp();
// Updates the active radio button.
self.notify_global_setting();
let sensitive =
settings.account_enabled() && settings.session_enabled() && !self.global_loading();
imp.global.set_sensitive(sensitive);
}
fn set_account_loading(&self, loading: bool) {
@ -176,4 +248,47 @@ impl NotificationsPage {
settings.set_session_enabled(imp.session_row.is_active());
}
fn set_global_loading(&self, loading: bool, setting: NotificationsGlobalSetting) {
let imp = self.imp();
// Only show the spinner on the selected one.
imp.global_all_bin
.set_is_loading(loading && setting == NotificationsGlobalSetting::All);
imp.global_direct_bin
.set_is_loading(loading && setting == NotificationsGlobalSetting::DirectAndMentions);
imp.global_mentions_bin
.set_is_loading(loading && setting == NotificationsGlobalSetting::MentionsOnly);
self.imp().global_loading.set(loading);
self.notify_global_loading();
}
#[template_callback]
fn global_setting_changed(&self, setting: NotificationsGlobalSetting) {
let Some(settings) = self.notifications_settings() else {
return;
};
let imp = self.imp();
if setting == settings.global_setting() {
// Nothing to do.
return;
}
imp.global.set_sensitive(false);
self.set_global_loading(true, setting);
spawn!(clone!(@weak self as obj, @weak settings => async move {
if settings.set_global_setting(setting).await.is_err() {
toast!(
obj,
gettext("Could not change global notifications setting")
);
}
obj.set_global_loading(false, setting);
obj.update_global();
}));
}
}

View File

@ -36,5 +36,62 @@
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="global">
<property name="title" translatable="yes">Global</property>
<property name="description" translatable="yes">Which messages trigger notifications in rooms that do not have more specific rules.</property>
<child>
<object class="AdwActionRow">
<property name="activatable-widget">global_all_radio</property>
<property name="title" translatable="yes">All messages in all rooms</property>
<child type="prefix">
<object class="LoadingBin" id="global_all_bin">
<property name="child">
<object class="GtkCheckButton" id="global_all_radio">
<property name="valign">center</property>
<property name="action-name">notifications.set-global-default</property>
<property name="action-target">'all'</property>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="activatable-widget">global_direct_radio</property>
<property name="title" translatable="yes">All messages in direct chats, and mentions and keywords in all rooms</property>
<child type="prefix">
<object class="LoadingBin" id="global_direct_bin">
<property name="child">
<object class="GtkCheckButton" id="global_direct_radio">
<property name="valign">center</property>
<property name="action-name">notifications.set-global-default</property>
<property name="action-target">'direct-and-mentions'</property>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="activatable-widget">global_mentions_radio</property>
<property name="title" translatable="yes">Only mentions and keywords in all rooms</property>
<child type="prefix">
<object class="LoadingBin" id="global_mentions_bin">
<property name="child">
<object class="GtkCheckButton" id="global_mentions_radio">
<property name="valign">center</property>
<property name="action-name">notifications.set-global-default</property>
<property name="action-target">'mentions-only'</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>