Browse Source

session: Synchronize the media previews safety settings with the Matrix account data

To share the setting between clients.
fractal-12
Kévin Commaille 10 months ago committed by Kévin Commaille
parent
commit
3388795df5
  1. 1
      po/POTFILES.in
  2. 30
      src/components/avatar/mod.rs
  3. 285
      src/session/model/global_account_data.rs
  4. 2
      src/session/model/mod.rs
  5. 2
      src/session/model/notifications/mod.rs
  6. 27
      src/session/model/session.rs
  7. 184
      src/session/model/session_settings.rs
  8. 225
      src/session/view/account_settings/safety_page/mod.rs
  9. 15
      src/session/view/account_settings/safety_page/mod.ui
  10. 5
      src/session/view/content/room_details/history_viewer/visual_media_item.rs
  11. 23
      src/session/view/content/room_history/message_row/visual_media.rs

1
po/POTFILES.in

@ -101,6 +101,7 @@ src/session/view/account_settings/notifications_page.ui
src/session/view/account_settings/safety_page/ignored_users_subpage/ignored_user_row.rs
src/session/view/account_settings/safety_page/ignored_users_subpage/ignored_user_row.ui
src/session/view/account_settings/safety_page/ignored_users_subpage/mod.ui
src/session/view/account_settings/safety_page/mod.rs
src/session/view/account_settings/safety_page/mod.ui
src/session/view/account_settings/user_session/user_session_list_subpage.ui
src/session/view/account_settings/user_session/user_session_row.ui

30
src/components/avatar/mod.rs

@ -80,7 +80,7 @@ mod imp {
paintable_ref: RefCell<Option<CountedRef>>,
paintable_animation_ref: RefCell<Option<CountedRef>>,
watched_room_handler: RefCell<Option<glib::SignalHandlerId>>,
watched_session_settings_handler: RefCell<Option<glib::SignalHandlerId>>,
watched_global_account_data_handler: RefCell<Option<glib::SignalHandlerId>>,
}
#[glib::object_subclass]
@ -200,8 +200,8 @@ mod imp {
));
self.watched_room_handler.replace(Some(room_handler));
let session_settings_handler = session
.settings()
let global_account_data_handler = session
.global_account_data()
.connect_media_previews_enabled_changed(clone!(
#[weak(rename_to = imp)]
self,
@ -209,8 +209,8 @@ mod imp {
imp.update_paintable();
}
));
self.watched_session_settings_handler
.replace(Some(session_settings_handler));
self.watched_global_account_data_handler
.replace(Some(global_account_data_handler));
}
AvatarImageSafetySetting::InviteAvatars => {
let room_handler = room.connect_is_invite_notify(clone!(
@ -222,8 +222,8 @@ mod imp {
));
self.watched_room_handler.replace(Some(room_handler));
let session_settings_handler = session
.settings()
let global_account_data_handler = session
.global_account_data()
.connect_invite_avatars_enabled_notify(clone!(
#[weak(rename_to = imp)]
self,
@ -231,8 +231,8 @@ mod imp {
imp.update_paintable();
}
));
self.watched_session_settings_handler
.replace(Some(session_settings_handler));
self.watched_global_account_data_handler
.replace(Some(global_account_data_handler));
}
}
@ -246,9 +246,9 @@ mod imp {
room.disconnect(handler);
}
if let Some(handler) = self.watched_session_settings_handler.take() {
if let Some(handler) = self.watched_global_account_data_handler.take() {
room.session()
.inspect(|session| session.settings().disconnect(handler));
.inspect(|session| session.global_account_data().disconnect(handler));
}
}
}
@ -271,11 +271,11 @@ mod imp {
match watched_safety_setting {
AvatarImageSafetySetting::None => unreachable!(),
AvatarImageSafetySetting::MediaPreviews => {
session.settings().should_room_show_media_previews(&room)
}
AvatarImageSafetySetting::MediaPreviews => session
.global_account_data()
.should_room_show_media_previews(&room),
AvatarImageSafetySetting::InviteAvatars => {
!room.is_invite() || session.settings().invite_avatars_enabled()
!room.is_invite() || session.global_account_data().invite_avatars_enabled()
}
}
}

285
src/session/model/global_account_data.rs

@ -0,0 +1,285 @@
use futures_util::StreamExt;
use gtk::{
glib,
glib::{clone, closure_local},
prelude::*,
subclass::prelude::*,
};
use ruma::events::media_preview_config::{
InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews,
};
use tokio::task::AbortHandle;
use tracing::error;
use super::{Room, Session};
use crate::{spawn, spawn_tokio};
mod imp {
use std::{
cell::{Cell, OnceCell, RefCell},
sync::LazyLock,
};
use glib::subclass::Signal;
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::GlobalAccountData)]
pub struct GlobalAccountData {
/// The session this account data belongs to.
#[property(get, construct_only)]
session: OnceCell<Session>,
/// Which rooms display media previews for this session.
pub(super) media_previews_enabled: RefCell<MediaPreviews>,
/// Whether to display avatars in invites.
#[property(get, default = true)]
invite_avatars_enabled: Cell<bool>,
abort_handle: RefCell<Option<AbortHandle>>,
}
#[glib::object_subclass]
impl ObjectSubclass for GlobalAccountData {
const NAME: &'static str = "GlobalAccountData";
type Type = super::GlobalAccountData;
}
#[glib::derived_properties]
impl ObjectImpl for GlobalAccountData {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> =
LazyLock::new(|| vec![Signal::builder("media-previews-enabled-changed").build()]);
SIGNALS.as_ref()
}
fn constructed(&self) {
self.parent_constructed();
spawn!(clone!(
#[weak(rename_to = imp)]
self,
async move {
imp.init_media_previews_settings().await;
imp.apply_migrations().await;
}
));
}
fn dispose(&self) {
if let Some(handle) = self.abort_handle.take() {
handle.abort();
}
}
}
impl GlobalAccountData {
/// The session these settings are for.
fn session(&self) -> &Session {
self.session.get().expect("session should be initialized")
}
/// Initialize the media previews settings from the account data and
/// watch for changes.
pub(super) async fn init_media_previews_settings(&self) {
let client = self.session().client();
let handle =
spawn_tokio!(async move { client.account().observe_media_preview_config().await });
let (account_data, stream) = match handle.await.expect("task was not aborted") {
Ok((account_data, stream)) => (account_data, stream),
Err(error) => {
error!("Could not initialize media preview settings: {error}");
return;
}
};
self.update_media_previews_settings(account_data);
let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
let fut = stream.for_each(move |account_data| {
let obj_weak = obj_weak.clone();
async move {
let ctx = glib::MainContext::default();
ctx.spawn(async move {
spawn!(async move {
if let Some(obj) = obj_weak.upgrade() {
obj.imp().update_media_previews_settings(account_data);
}
});
});
}
});
let abort_handle = spawn_tokio!(fut).abort_handle();
self.abort_handle.replace(Some(abort_handle));
}
/// Update the media previews settings with the given account data.
fn update_media_previews_settings(&self, account_data: MediaPreviewConfigEventContent) {
let media_previews_enabled_changed =
*self.media_previews_enabled.borrow() != account_data.media_previews;
if media_previews_enabled_changed {
*self.media_previews_enabled.borrow_mut() = account_data.media_previews;
self.obj()
.emit_by_name::<()>("media-previews-enabled-changed", &[]);
}
let account_data_invite_avatars_enabled =
account_data.invite_avatars == InviteAvatars::On;
let invite_avatars_enabled_changed =
self.invite_avatars_enabled.get() != account_data_invite_avatars_enabled;
if invite_avatars_enabled_changed {
self.invite_avatars_enabled
.set(account_data_invite_avatars_enabled);
self.obj().notify_invite_avatars_enabled();
}
}
/// Apply any necessary migrations.
pub(super) async fn apply_migrations(&self) {
let session_settings = self.session().settings();
let mut stored_settings = session_settings.stored_settings();
if stored_settings.version != 0 {
// No migration to apply.
return;
}
// Align the account data with the stored settings.
let stored_media_previews_enabled = stored_settings
.media_previews_enabled
.take()
.map(|setting| setting.global)
.unwrap_or_default();
let _ = self
.set_media_previews_enabled(stored_media_previews_enabled.into())
.await;
let stored_media_previews_enabled = stored_settings
.invite_avatars_enabled
.take()
.unwrap_or(true);
let _ = self
.set_invite_avatars_enabled(stored_media_previews_enabled)
.await;
session_settings.apply_version_1_migration();
}
/// Set which rooms display media previews.
pub(super) async fn set_media_previews_enabled(
&self,
setting: MediaPreviews,
) -> Result<(), ()> {
if *self.media_previews_enabled.borrow() == setting {
return Ok(());
}
let client = self.session().client();
let setting_clone = setting.clone();
let handle = spawn_tokio!(async move {
client
.account()
.set_media_previews_display_policy(setting_clone)
.await
});
if let Err(error) = handle.await.expect("task was not aborted") {
error!("Could not change media previews enabled setting: {error}");
return Err(());
}
self.media_previews_enabled.replace(setting);
self.obj()
.emit_by_name::<()>("media-previews-enabled-changed", &[]);
Ok(())
}
/// Set whether to display avatars in invites.
pub(super) async fn set_invite_avatars_enabled(&self, enabled: bool) -> Result<(), ()> {
if self.invite_avatars_enabled.get() == enabled {
return Ok(());
}
let client = self.session().client();
let setting = if enabled {
InviteAvatars::On
} else {
InviteAvatars::Off
};
let handle = spawn_tokio!(async move {
client
.account()
.set_invite_avatars_display_policy(setting)
.await
});
if let Err(error) = handle.await.expect("task was not aborted") {
error!("Could not change invite avatars enabled setting: {error}");
return Err(());
}
self.invite_avatars_enabled.set(enabled);
self.obj().notify_invite_avatars_enabled();
Ok(())
}
}
}
glib::wrapper! {
/// The settings in the global account data of a [`Session`].
pub struct GlobalAccountData(ObjectSubclass<imp::GlobalAccountData>);
}
impl GlobalAccountData {
/// Create a new `GlobalAccountData` for the given session.
pub(crate) fn new(session: &Session) -> Self {
glib::Object::builder::<Self>()
.property("session", session)
.build()
}
/// Which rooms display media previews.
pub(crate) fn media_previews_enabled(&self) -> MediaPreviews {
self.imp().media_previews_enabled.borrow().clone()
}
/// Whether the given room should display media previews.
pub(crate) fn should_room_show_media_previews(&self, room: &Room) -> bool {
match &*self.imp().media_previews_enabled.borrow() {
MediaPreviews::Off => false,
MediaPreviews::Private => !room.join_rule().anyone_can_join(),
_ => true,
}
}
/// Set which rooms display media previews.
pub(crate) async fn set_media_previews_enabled(
&self,
setting: MediaPreviews,
) -> Result<(), ()> {
self.imp().set_media_previews_enabled(setting).await
}
/// Set whether to display avatars in invites.
pub(crate) async fn set_invite_avatars_enabled(&self, enabled: bool) -> Result<(), ()> {
self.imp().set_invite_avatars_enabled(enabled).await
}
/// Connect to the signal emitted when the media previews setting changed.
pub fn connect_media_previews_enabled_changed<F: Fn(&Self) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_closure(
"media-previews-enabled-changed",
true,
closure_local!(move |obj: Self| {
f(&obj);
}),
)
}
}

2
src/session/model/mod.rs

@ -1,3 +1,4 @@
mod global_account_data;
mod ignored_users;
mod notifications;
mod remote;
@ -12,6 +13,7 @@ mod user_sessions_list;
mod verification;
pub(crate) use self::{
global_account_data::*,
ignored_users::IgnoredUsers,
notifications::{
Notifications, NotificationsGlobalSetting, NotificationsRoomSetting, NotificationsSettings,

2
src/session/model/notifications/mod.rs

@ -279,7 +279,7 @@ impl Notifications {
format!("{session_id}//{matrix_uri}//{random_id}")
};
let inhibit_image = is_invite && !session.settings().invite_avatars_enabled();
let inhibit_image = is_invite && !session.global_account_data().invite_avatars_enabled();
let icon = room.avatar_data().as_notification_icon(inhibit_image).await;
Self::send_notification(

27
src/session/model/session.rs

@ -18,8 +18,8 @@ use tokio_stream::wrappers::BroadcastStream;
use tracing::{debug, error, info};
use super::{
IgnoredUsers, Notifications, RemoteCache, RoomList, SessionSecurity, SessionSettings,
SidebarItemList, SidebarListModel, User, UserSessionsList, VerificationList,
GlobalAccountData, IgnoredUsers, Notifications, RemoteCache, RoomList, SessionSecurity,
SessionSettings, SidebarItemList, SidebarListModel, User, UserSessionsList, VerificationList,
};
use crate::{
components::AvatarData,
@ -91,6 +91,9 @@ mod imp {
/// The current settings for this session.
#[property(get, construct_only)]
settings: OnceCell<SessionSettings>,
/// The settings in the global account data for this session.
#[property(get = Self::global_account_data_owned)]
global_account_data: OnceCell<GlobalAccountData>,
/// The notifications API for this session.
#[property(get)]
notifications: Notifications,
@ -335,8 +338,20 @@ mod imp {
self.obj().notify_is_offline();
}
/// The settings stored in the global account data for this session.
fn global_account_data(&self) -> &GlobalAccountData {
self.global_account_data
.get_or_init(|| GlobalAccountData::new(&self.obj()))
}
/// The owned settings stored in the global account data for this
/// session.
fn global_account_data_owned(&self) -> GlobalAccountData {
self.global_account_data().clone()
}
/// The cache for remote data.
pub(crate) fn remote_cache(&self) -> &RemoteCache {
pub(super) fn remote_cache(&self) -> &RemoteCache {
self.remote_cache
.get_or_init(|| RemoteCache::new(self.obj().clone()))
}
@ -356,6 +371,8 @@ mod imp {
}
)
);
self.global_account_data();
self.watch_session_changes();
self.update_homeserver_reachable().await;
@ -540,7 +557,7 @@ mod imp {
Err(error) => {
error!(
session = self.obj().session_id(),
"Failed to deserialize session profile: {error}"
"Could not deserialize session profile: {error}"
);
return;
}
@ -611,7 +628,7 @@ mod imp {
Err(error) => {
error!(
session = self.obj().session_id(),
"Failed to serialize session profile: {error}"
"Could not serialize session profile: {error}"
);
return;
}

184
src/session/model/session_settings.rs

@ -1,16 +1,24 @@
use std::collections::BTreeSet;
use gtk::{glib, glib::closure_local, prelude::*, subclass::prelude::*};
use gtk::{glib, prelude::*, subclass::prelude::*};
use indexmap::IndexSet;
use ruma::{serde::SerializeAsRefStr, OwnedServerName};
use ruma::{events::media_preview_config::MediaPreviews, OwnedServerName};
use serde::{Deserialize, Serialize};
use tracing::info;
use super::{Room, SidebarSectionName};
use super::SidebarSectionName;
use crate::{session_list::SessionListSettings, Application};
/// The current version of the stored session settings.
const CURRENT_VERSION: u8 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct StoredSessionSettings {
/// The version of the stored settings.
#[serde(default)]
pub(super) version: u8,
/// Custom servers to explore.
#[serde(default, skip_serializing_if = "IndexSet::is_empty")]
explore_custom_servers: IndexSet<OwnedServerName>,
@ -41,27 +49,29 @@ pub(crate) struct StoredSessionSettings {
sections_expanded: SectionsExpanded,
/// Which rooms display media previews for this session.
#[serde(default, skip_serializing_if = "ruma::serde::is_default")]
media_previews_enabled: MediaPreviewsSetting,
///
/// Legacy setting from version 0 of the stored settings.
#[serde(skip_serializing)]
pub(super) media_previews_enabled: Option<MediaPreviewsSetting>,
/// Whether to display avatars in invites.
#[serde(
default = "ruma::serde::default_true",
skip_serializing_if = "ruma::serde::is_true"
)]
invite_avatars_enabled: bool,
///
/// Legacy setting from version 0 of the stored settings.
#[serde(skip_serializing)]
pub(super) invite_avatars_enabled: Option<bool>,
}
impl Default for StoredSessionSettings {
fn default() -> Self {
Self {
version: CURRENT_VERSION,
explore_custom_servers: Default::default(),
notifications_enabled: true,
public_read_receipts_enabled: true,
typing_enabled: true,
sections_expanded: Default::default(),
media_previews_enabled: Default::default(),
invite_avatars_enabled: true,
invite_avatars_enabled: Default::default(),
}
}
}
@ -70,11 +80,8 @@ mod imp {
use std::{
cell::{OnceCell, RefCell},
marker::PhantomData,
sync::LazyLock,
};
use glib::subclass::Signal;
use super::*;
#[derive(Debug, Default, glib::Properties)]
@ -94,9 +101,6 @@ mod imp {
/// Whether typing notifications are enabled for this session.
#[property(get = Self::typing_enabled, set = Self::set_typing_enabled, explicit_notify, default = true)]
typing_enabled: PhantomData<bool>,
/// Whether to display avatars in invites.
#[property(get = Self::invite_avatars_enabled, set = Self::set_invite_avatars_enabled, explicit_notify, default = true)]
invite_avatars_enabled: PhantomData<bool>,
}
#[glib::object_subclass]
@ -106,13 +110,7 @@ mod imp {
}
#[glib::derived_properties]
impl ObjectImpl for SessionSettings {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> =
LazyLock::new(|| vec![Signal::builder("media-previews-enabled-changed").build()]);
SIGNALS.as_ref()
}
}
impl ObjectImpl for SessionSettings {}
impl SessionSettings {
/// Whether notifications are enabled for this session.
@ -165,26 +163,33 @@ mod imp {
self.obj().notify_typing_enabled();
}
/// Whether to display avatars in invites.
fn invite_avatars_enabled(&self) -> bool {
self.stored_settings.borrow().invite_avatars_enabled
}
/// Apply the migration of the stored settings from version 0 to version
/// 1.
pub(crate) fn apply_version_1_migration(&self) {
{
let mut stored_settings = self.stored_settings.borrow_mut();
/// Set whether to display avatars in invites.
fn set_invite_avatars_enabled(&self, enabled: bool) {
if self.invite_avatars_enabled() == enabled {
return;
if stored_settings.version > 0 {
return;
}
info!(
session = self.obj().session_id(),
"Migrating store session to version 1"
);
stored_settings.media_previews_enabled.take();
stored_settings.invite_avatars_enabled.take();
stored_settings.version = 1;
}
self.stored_settings.borrow_mut().invite_avatars_enabled = enabled;
session_list_settings().save();
self.obj().notify_invite_avatars_enabled();
}
}
}
glib::wrapper! {
/// The settings of a `Session`.
/// The settings of a [`Session`](super::Session).
pub struct SessionSettings(ObjectSubclass<imp::SessionSettings>);
}
@ -199,9 +204,7 @@ impl SessionSettings {
/// Restore existing `SessionSettings` with the given session ID and stored
/// settings.
pub(crate) fn restore(session_id: &str, stored_settings: StoredSessionSettings) -> Self {
let obj = glib::Object::builder::<Self>()
.property("session-id", session_id)
.build();
let obj = Self::new(session_id);
*obj.imp().stored_settings.borrow_mut() = stored_settings;
obj
}
@ -211,6 +214,11 @@ impl SessionSettings {
self.imp().stored_settings.borrow().clone()
}
/// Apply the migration of the stored settings from version 0 to version 1.
pub(crate) fn apply_version_1_migration(&self) {
self.imp().apply_version_1_migration();
}
/// Delete the settings from the application settings.
pub(crate) fn delete(&self) {
session_list_settings().remove(&self.session_id());
@ -256,49 +264,6 @@ impl SessionSettings {
.set_section_expanded(section_name, expanded);
session_list_settings().save();
}
/// Whether the given room should display media previews.
pub(crate) fn should_room_show_media_previews(&self, room: &Room) -> bool {
self.imp()
.stored_settings
.borrow()
.media_previews_enabled
.should_room_show_media_previews(room)
}
/// Which rooms display media previews.
pub(crate) fn media_previews_global_enabled(&self) -> MediaPreviewsGlobalSetting {
self.imp()
.stored_settings
.borrow()
.media_previews_enabled
.global
}
/// Set which rooms display media previews.
pub(crate) fn set_media_previews_global_enabled(&self, setting: MediaPreviewsGlobalSetting) {
self.imp()
.stored_settings
.borrow_mut()
.media_previews_enabled
.global = setting;
session_list_settings().save();
self.emit_by_name::<()>("media-previews-enabled-changed", &[]);
}
/// Connect to the signal emitted when the media previews setting changed.
pub fn connect_media_previews_enabled_changed<F: Fn(&Self) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_closure(
"media-previews-enabled-changed",
true,
closure_local!(move |obj: Self| {
f(&obj);
}),
)
}
}
/// The sections that are expanded.
@ -338,35 +303,22 @@ impl Default for SectionsExpanded {
}
/// Setting about which rooms display media previews.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct MediaPreviewsSetting {
///
/// Legacy setting from version 0 of the stored settings.
#[derive(Debug, Clone, Default, Deserialize)]
pub(super) struct MediaPreviewsSetting {
/// The default setting for all rooms.
#[serde(default, skip_serializing_if = "ruma::serde::is_default")]
global: MediaPreviewsGlobalSetting,
}
impl MediaPreviewsSetting {
// Whether the given room should show room previews according to this setting.
pub(crate) fn should_room_show_media_previews(&self, room: &Room) -> bool {
self.global.should_room_show_media_previews(room)
}
#[serde(default)]
pub(super) global: MediaPreviewsGlobalSetting,
}
/// Possible values of the global setting about which rooms display media
/// previews.
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
strum::AsRefStr,
strum::EnumString,
SerializeAsRefStr,
)]
#[strum(serialize_all = "kebab-case")]
pub(crate) enum MediaPreviewsGlobalSetting {
///
/// Legacy setting from version 0 of the stored settings.
#[derive(Debug, Clone, Default, strum::EnumString)]
#[strum(serialize_all = "lowercase")]
pub(super) enum MediaPreviewsGlobalSetting {
/// All rooms show media previews.
All,
/// Only private rooms show media previews.
@ -376,18 +328,6 @@ pub(crate) enum MediaPreviewsGlobalSetting {
None,
}
impl MediaPreviewsGlobalSetting {
/// Whether the given room should show room previews according to this
/// setting.
pub(crate) fn should_room_show_media_previews(self, room: &Room) -> bool {
match self {
Self::All => true,
Self::Private => !room.join_rule().anyone_can_join(),
Self::None => false,
}
}
}
impl<'de> Deserialize<'de> for MediaPreviewsGlobalSetting {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -398,6 +338,16 @@ impl<'de> Deserialize<'de> for MediaPreviewsGlobalSetting {
}
}
impl From<MediaPreviewsGlobalSetting> for MediaPreviews {
fn from(value: MediaPreviewsGlobalSetting) -> Self {
match value {
MediaPreviewsGlobalSetting::All => Self::On,
MediaPreviewsGlobalSetting::Private => Self::Private,
MediaPreviewsGlobalSetting::None => Self::Off,
}
}
}
/// The session list settings of the application.
fn session_list_settings() -> SessionListSettings {
Application::default().session_list().settings()

225
src/session/view/account_settings/safety_page/mod.rs

@ -1,17 +1,23 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, glib::clone, CompositeTemplate};
use ruma::events::media_preview_config::MediaPreviews;
use tracing::error;
mod ignored_users_subpage;
pub(super) use self::ignored_users_subpage::IgnoredUsersSubpage;
use crate::{
components::ButtonCountRow,
session::model::{MediaPreviewsGlobalSetting, Session},
components::{ButtonCountRow, CheckLoadingRow, SwitchLoadingRow},
session::model::Session,
spawn, toast,
};
mod imp {
use std::{cell::RefCell, marker::PhantomData};
use std::{
cell::{Cell, RefCell},
marker::PhantomData,
};
use glib::subclass::InitializingObject;
@ -28,15 +34,29 @@ mod imp {
#[template_child]
ignored_users_row: TemplateChild<ButtonCountRow>,
#[template_child]
invite_avatars_row: TemplateChild<adw::SwitchRow>,
media_previews: TemplateChild<adw::PreferencesGroup>,
#[template_child]
media_previews_on_row: TemplateChild<CheckLoadingRow>,
#[template_child]
media_previews_private_row: TemplateChild<CheckLoadingRow>,
#[template_child]
media_previews_off_row: TemplateChild<CheckLoadingRow>,
#[template_child]
invite_avatars_row: TemplateChild<SwitchLoadingRow>,
/// The current session.
#[property(get, set = Self::set_session, nullable)]
session: glib::WeakRef<Session>,
/// The media previews setting, as a string.
#[property(get = Self::media_previews_enabled, set = Self::set_media_previews_enabled)]
media_previews_enabled: PhantomData<String>,
/// Whether the media previews section is busy.
#[property(get)]
media_previews_loading: Cell<bool>,
/// Whether the invite avatars row is busy.
#[property(get)]
invite_avatars_loading: Cell<bool>,
ignored_users_count_handler: RefCell<Option<glib::SignalHandlerId>>,
session_settings_handler: RefCell<Option<glib::SignalHandlerId>>,
global_account_data_handlers: RefCell<Vec<glib::SignalHandlerId>>,
bindings: RefCell<Vec<glib::Binding>>,
}
@ -48,6 +68,7 @@ mod imp {
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
Self::bind_template_callbacks(klass);
klass.install_property_action(
"safety.set-media-previews-enabled",
@ -63,13 +84,14 @@ mod imp {
#[glib::derived_properties]
impl ObjectImpl for SafetyPage {
fn dispose(&self) {
self.clear();
self.disconnect_signals();
}
}
impl WidgetImpl for SafetyPage {}
impl PreferencesPageImpl for SafetyPage {}
#[gtk::template_callbacks]
impl SafetyPage {
/// Set the current session.
fn set_session(&self, session: Option<&Session>) {
@ -77,8 +99,7 @@ mod imp {
return;
}
self.clear();
let obj = self.obj();
self.disconnect_signals();
if let Some(session) = session {
let ignored_users = session.ignored_users();
@ -96,19 +117,28 @@ mod imp {
self.ignored_users_count_handler
.replace(Some(ignored_users_count_handler));
let session_settings = session.settings();
let global_account_data = session.global_account_data();
let media_previews_handler = session_settings
let media_previews_handler = global_account_data
.connect_media_previews_enabled_changed(clone!(
#[weak]
obj,
#[weak(rename_to = imp)]
self,
move |_| {
imp.update_media_previews();
}
));
let invite_avatars_handler = global_account_data
.connect_invite_avatars_enabled_notify(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
// Update the active media previews radio button.
obj.notify_media_previews_enabled();
imp.update_invite_avatars();
}
));
self.session_settings_handler
.replace(Some(media_previews_handler));
self.global_account_data_handlers
.replace(vec![media_previews_handler, invite_avatars_handler]);
let session_settings = session.settings();
let public_read_receipts_binding = session_settings
.bind_property(
@ -124,28 +154,16 @@ mod imp {
.bidirectional()
.sync_create()
.build();
let invite_avatars_binding = session_settings
.bind_property(
"invite-avatars-enabled",
&*self.invite_avatars_row,
"active",
)
.bidirectional()
.sync_create()
.build();
self.bindings.replace(vec![
public_read_receipts_binding,
typing_binding,
invite_avatars_binding,
]);
self.bindings
.replace(vec![public_read_receipts_binding, typing_binding]);
}
self.session.set(session);
// Update the active media previews radio button.
obj.notify_media_previews_enabled();
obj.notify_session();
self.update_media_previews();
self.update_invite_avatars();
self.obj().notify_session();
}
/// The media previews setting, as a string.
@ -154,38 +172,147 @@ mod imp {
return String::new();
};
session
.settings()
.media_previews_global_enabled()
.as_ref()
.to_owned()
match session.global_account_data().media_previews_enabled() {
MediaPreviews::Off => "off",
MediaPreviews::Private => "private",
_ => "on",
}
.to_owned()
}
/// Update the media previews section.
fn update_media_previews(&self) {
// Updates the active radio button.
self.obj().notify_media_previews_enabled();
self.media_previews
.set_sensitive(!self.media_previews_loading.get());
}
/// Set the media previews setting, as a string.
fn set_media_previews_enabled(&self, setting: &str) {
if setting.is_empty() {
error!("Invalid empty value to set media previews setting");
return;
}
let setting = setting.into();
spawn!(clone!(
#[weak(rename_to = imp)]
self,
async move {
imp.set_media_previews_enabled_inner(setting).await;
}
));
}
/// Propagate the media previews setting.
async fn set_media_previews_enabled_inner(&self, setting: MediaPreviews) {
let Some(session) = self.session.upgrade() else {
return;
};
let global_account_data = session.global_account_data();
let Ok(setting) = setting.parse::<MediaPreviewsGlobalSetting>() else {
error!("Invalid value to set global media previews setting: {setting}");
if setting == global_account_data.media_previews_enabled() {
// Nothing to do.
return;
}
self.media_previews.set_sensitive(false);
self.set_media_previews_loading(true, &setting);
if global_account_data
.set_media_previews_enabled(setting.clone())
.await
.is_err()
{
toast!(
self.obj(),
gettext("Could not change media previews setting"),
);
}
self.set_media_previews_loading(false, &setting);
self.update_media_previews();
}
/// Set the loading state of the media previews section.
fn set_media_previews_loading(&self, loading: bool, setting: &MediaPreviews) {
// Only show the spinner on the selected one.
self.media_previews_on_row
.set_is_loading(loading && *setting == MediaPreviews::On);
self.media_previews_private_row
.set_is_loading(loading && *setting == MediaPreviews::Private);
self.media_previews_off_row
.set_is_loading(loading && *setting == MediaPreviews::Off);
self.media_previews_loading.set(loading);
self.obj().notify_media_previews_loading();
}
/// Update the invite avatars section.
fn update_invite_avatars(&self) {
let Some(session) = self.session.upgrade() else {
return;
};
session
.settings()
.set_media_previews_global_enabled(setting);
self.invite_avatars_row
.set_is_active(session.global_account_data().invite_avatars_enabled());
self.invite_avatars_row
.set_sensitive(!self.invite_avatars_loading.get());
}
/// Reset the signal handlers and bindings.
fn clear(&self) {
/// Set the invite avatars setting.
#[template_callback]
async fn set_invite_avatars_enabled(&self) {
let Some(session) = self.session.upgrade() else {
return;
};
let global_account_data = session.global_account_data();
let enabled = self.invite_avatars_row.is_active();
if enabled == global_account_data.invite_avatars_enabled() {
// Nothing to do.
return;
}
self.invite_avatars_row.set_sensitive(false);
self.set_invite_avatars_loading(true);
if global_account_data
.set_invite_avatars_enabled(enabled)
.await
.is_err()
{
let msg = if enabled {
gettext("Could not enable avatars for invites")
} else {
gettext("Could not disable avatars for invites")
};
toast!(self.obj(), msg);
}
self.set_invite_avatars_loading(false);
self.update_invite_avatars();
}
/// Set the loading state of the invite avatars section.
fn set_invite_avatars_loading(&self, loading: bool) {
self.invite_avatars_loading.set(loading);
self.obj().notify_invite_avatars_loading();
}
/// Disconnect the signal handlers and bindings.
fn disconnect_signals(&self) {
if let Some(session) = self.session.upgrade() {
if let Some(handler) = self.ignored_users_count_handler.take() {
session.ignored_users().disconnect(handler);
let global_account_data = session.global_account_data();
for handler in self.global_account_data_handlers.take() {
global_account_data.disconnect(handler);
}
if let Some(handler) = self.session_settings_handler.take() {
session.settings().disconnect(handler);
if let Some(handler) = self.ignored_users_count_handler.take() {
session.ignored_users().disconnect(handler);
}
}

15
src/session/view/account_settings/safety_page/mod.ui

@ -36,14 +36,14 @@
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<object class="AdwPreferencesGroup" id="media_previews">
<property name="title" translatable="yes">Media Previews</property>
<property name="description" translatable="yes">Which rooms automatically show previews for images and videos. Hidden previews can always be shown by clicking on the media.</property>
<child>
<object class="CheckLoadingRow" id="media_previews_all_row">
<object class="CheckLoadingRow" id="media_previews_on_row">
<property name="title" translatable="yes">Show in all rooms</property>
<property name="action-name">safety.set-media-previews-enabled</property>
<property name="action-target">'all'</property>
<property name="action-target">'on'</property>
</object>
</child>
<child>
@ -54,10 +54,10 @@
</object>
</child>
<child>
<object class="CheckLoadingRow" id="media_previews_public_row">
<object class="CheckLoadingRow" id="media_previews_off_row">
<property name="title" translatable="yes">Hide in all rooms</property>
<property name="action-name">safety.set-media-previews-enabled</property>
<property name="action-target">'none'</property>
<property name="action-target">'off'</property>
</object>
</child>
</object>
@ -65,10 +65,11 @@
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwSwitchRow" id="invite_avatars_row">
<property name="selectable">False</property>
<object class="SwitchLoadingRow" id="invite_avatars_row">
<property name="title" translatable="yes">Show Avatars for Invites</property>
<property name="subtitle" translatable="yes">Display the avatars of the room and the inviter</property>
<property name="is-loading" bind-source="SafetyPage" bind-property="invite-avatars-loading" bind-flags="sync-create"/>
<signal name="notify::is-active" handler="set_invite_avatars_enabled" swapped="true"/>
</object>
</child>
</object>

5
src/session/view/content/room_details/history_viewer/visual_media_item.rs

@ -160,7 +160,10 @@ mod imp {
return;
};
if session.settings().should_room_show_media_previews(&room) {
if session
.global_account_data()
.should_room_show_media_previews(&room)
{
spawn!(
glib::Priority::LOW,
clone!(

23
src/session/view/content/room_history/message_row/visual_media.rs

@ -71,7 +71,7 @@ mod imp {
/// The room where the message was sent.
room: glib::WeakRef<Room>,
join_rule_handler: RefCell<Option<glib::SignalHandlerId>>,
session_settings_handler: RefCell<Option<glib::SignalHandlerId>>,
global_account_data_handler: RefCell<Option<glib::SignalHandlerId>>,
/// The visual media message to display.
media_message: RefCell<Option<VisualMediaMessage>>,
/// The cache key for the current media message.
@ -298,7 +298,9 @@ mod imp {
self.spinner.set_visible(state == LoadingState::Loading);
self.hide_preview_button.set_visible(
state == LoadingState::Ready
&& !session.settings().should_room_show_media_previews(&room),
&& !session
.global_account_data()
.should_room_show_media_previews(&room),
);
self.error.set_visible(state == LoadingState::Error);
@ -482,8 +484,8 @@ mod imp {
));
self.join_rule_handler.replace(Some(join_rule_handler));
let session_settings_handler = session
.settings()
let global_account_data_handler = session
.global_account_data()
.connect_media_previews_enabled_changed(clone!(
#[weak(rename_to = imp)]
self,
@ -491,8 +493,8 @@ mod imp {
imp.update_media();
}
));
self.session_settings_handler
.replace(Some(session_settings_handler));
self.global_account_data_handler
.replace(Some(global_account_data_handler));
self.room.set(Some(room));
@ -595,7 +597,10 @@ mod imp {
return;
};
if session.settings().should_room_show_media_previews(&room) {
if session
.global_account_data()
.should_room_show_media_previews(&room)
{
// Only load the media if it was not loaded before.
if self.state.get() == LoadingState::Initial {
self.show_media();
@ -796,9 +801,9 @@ mod imp {
room.join_rule().disconnect(handler);
}
if let Some(handler) = self.session_settings_handler.take() {
if let Some(handler) = self.global_account_data_handler.take() {
if let Some(session) = room.session() {
session.settings().disconnect(handler);
session.global_account_data().disconnect(handler);
}
}
}

Loading…
Cancel
Save