diff --git a/data/resources/icons/scalable/actions/hide-symbolic.svg b/data/resources/icons/scalable/actions/hide-symbolic.svg
new file mode 100644
index 00000000..6a4d6fc4
--- /dev/null
+++ b/data/resources/icons/scalable/actions/hide-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 310040e3..f5612240 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -18,6 +18,7 @@
icons/scalable/actions/go-bottom-symbolic.svg
icons/scalable/actions/go-next-symbolic.svg
icons/scalable/actions/go-previous-symbolic.svg
+ icons/scalable/actions/hide-symbolic.svg
icons/scalable/actions/idp-apple-dark.svg
icons/scalable/actions/idp-apple.svg
icons/scalable/actions/idp-facebook.svg
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6b139d01..0e249869 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -159,6 +159,7 @@ src/session/view/content/room_history/message_row/reaction_list.ui
src/session/view/content/room_history/message_row/reply.ui
src/session/view/content/room_history/message_row/text/widgets.rs
src/session/view/content/room_history/message_row/visual_media.rs
+src/session/view/content/room_history/message_row/visual_media.ui
src/session/view/content/room_history/message_toolbar/attachment_dialog.ui
src/session/view/content/room_history/message_toolbar/completion/completion_popover.rs
src/session/view/content/room_history/message_toolbar/mod.rs
diff --git a/src/session/model/mod.rs b/src/session/model/mod.rs
index 3e42e274..72b51622 100644
--- a/src/session/model/mod.rs
+++ b/src/session/model/mod.rs
@@ -23,7 +23,7 @@ pub(crate) use self::{
room_list::RoomList,
security::*,
session::*,
- session_settings::{SessionSettings, StoredSessionSettings},
+ session_settings::*,
sidebar_data::{
Selection, SidebarIconItem, SidebarIconItemType, SidebarItemList, SidebarListModel,
SidebarSection, SidebarSectionName,
diff --git a/src/session/model/notifications/notifications_settings.rs b/src/session/model/notifications/notifications_settings.rs
index 7bddb046..970c54e6 100644
--- a/src/session/model/notifications/notifications_settings.rs
+++ b/src/session/model/notifications/notifications_settings.rs
@@ -38,9 +38,7 @@ pub enum NotificationsGlobalSetting {
}
/// The possible values for a room notifications setting.
-#[derive(
- Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, strum::Display, strum::EnumString,
-)]
+#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, strum::EnumString)]
#[enum_type(name = "NotificationsRoomSetting")]
#[strum(serialize_all = "kebab-case")]
pub enum NotificationsRoomSetting {
diff --git a/src/session/model/session_settings.rs b/src/session/model/session_settings.rs
index be2513cd..c6b733a8 100644
--- a/src/session/model/session_settings.rs
+++ b/src/session/model/session_settings.rs
@@ -1,16 +1,16 @@
use std::collections::BTreeSet;
-use gtk::{glib, prelude::*, subclass::prelude::*};
+use gtk::{glib, glib::closure_local, prelude::*, subclass::prelude::*};
use indexmap::IndexSet;
-use ruma::OwnedServerName;
+use ruma::{serde::SerializeAsRefStr, OwnedServerName};
use serde::{Deserialize, Serialize};
-use super::SidebarSectionName;
-use crate::Application;
+use super::{Room, SidebarSectionName};
+use crate::{session_list::SessionListSettings, Application};
-#[derive(Debug, Clone, Serialize, Deserialize, glib::Boxed)]
-#[boxed_type(name = "StoredSessionSettings")]
-pub struct StoredSessionSettings {
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[allow(clippy::struct_excessive_bools)]
+pub(crate) struct StoredSessionSettings {
/// Custom servers to explore.
#[serde(default, skip_serializing_if = "IndexSet::is_empty")]
explore_custom_servers: IndexSet,
@@ -39,6 +39,10 @@ pub struct StoredSessionSettings {
/// The sections that are expanded.
#[serde(default)]
sections_expanded: SectionsExpanded,
+
+ /// Which rooms display media previews for this session.
+ #[serde(default, skip_serializing_if = "ruma::serde::is_default")]
+ media_previews_enabled: MediaPreviewsSetting,
}
impl Default for StoredSessionSettings {
@@ -49,6 +53,7 @@ impl Default for StoredSessionSettings {
public_read_receipts_enabled: true,
typing_enabled: true,
sections_expanded: Default::default(),
+ media_previews_enabled: Default::default(),
}
}
}
@@ -57,8 +62,11 @@ mod imp {
use std::{
cell::{OnceCell, RefCell},
marker::PhantomData,
+ sync::LazyLock,
};
+ use glib::subclass::Signal;
+
use super::*;
#[derive(Debug, Default, glib::Properties)]
@@ -66,19 +74,18 @@ mod imp {
pub struct SessionSettings {
/// The ID of the session these settings are for.
#[property(get, construct_only)]
- pub session_id: OnceCell,
+ session_id: OnceCell,
/// The stored settings.
- #[property(get, construct_only)]
- pub stored_settings: RefCell,
+ pub(super) stored_settings: RefCell,
/// Whether notifications are enabled for this session.
#[property(get = Self::notifications_enabled, set = Self::set_notifications_enabled, explicit_notify, default = true)]
- pub notifications_enabled: PhantomData,
+ notifications_enabled: PhantomData,
/// Whether public read receipts are enabled for this session.
#[property(get = Self::public_read_receipts_enabled, set = Self::set_public_read_receipts_enabled, explicit_notify, default = true)]
- pub public_read_receipts_enabled: PhantomData,
+ public_read_receipts_enabled: PhantomData,
/// Whether typing notifications are enabled for this session.
#[property(get = Self::typing_enabled, set = Self::set_typing_enabled, explicit_notify, default = true)]
- pub typing_enabled: PhantomData,
+ typing_enabled: PhantomData,
}
#[glib::object_subclass]
@@ -88,7 +95,13 @@ mod imp {
}
#[glib::derived_properties]
- impl ObjectImpl for SessionSettings {}
+ impl ObjectImpl for SessionSettings {
+ fn signals() -> &'static [Signal] {
+ static SIGNALS: LazyLock> =
+ LazyLock::new(|| vec![Signal::builder("media-previews-enabled-changed").build()]);
+ SIGNALS.as_ref()
+ }
+ }
impl SessionSettings {
/// Whether notifications are enabled for this session.
@@ -103,7 +116,7 @@ mod imp {
}
self.stored_settings.borrow_mut().notifications_enabled = enabled;
- super::SessionSettings::save();
+ session_list_settings().save();
self.obj().notify_notifications_enabled();
}
@@ -121,7 +134,7 @@ mod imp {
self.stored_settings
.borrow_mut()
.public_read_receipts_enabled = enabled;
- super::SessionSettings::save();
+ session_list_settings().save();
self.obj().notify_public_read_receipts_enabled();
}
@@ -137,7 +150,7 @@ mod imp {
}
self.stored_settings.borrow_mut().typing_enabled = enabled;
- super::SessionSettings::save();
+ session_list_settings().save();
self.obj().notify_typing_enabled();
}
}
@@ -150,33 +163,30 @@ glib::wrapper! {
impl SessionSettings {
/// Create a new `SessionSettings` for the given session ID.
- pub fn new(session_id: &str) -> Self {
+ pub(crate) fn new(session_id: &str) -> Self {
glib::Object::builder()
.property("session-id", session_id)
- .property("stored-settings", StoredSessionSettings::default())
.build()
}
/// Restore existing `SessionSettings` with the given session ID and stored
/// settings.
- pub fn restore(session_id: &str, stored_settings: &StoredSessionSettings) -> Self {
- glib::Object::builder()
+ pub(crate) fn restore(session_id: &str, stored_settings: StoredSessionSettings) -> Self {
+ let obj = glib::Object::builder::()
.property("session-id", session_id)
- .property("stored-settings", stored_settings)
- .build()
+ .build();
+ *obj.imp().stored_settings.borrow_mut() = stored_settings;
+ obj
}
- /// Save these settings in the application settings.
- fn save() {
- Application::default().session_list().settings().save();
+ /// The stored settings.
+ pub(crate) fn stored_settings(&self) -> StoredSessionSettings {
+ self.imp().stored_settings.borrow().clone()
}
/// Delete the settings from the application settings.
- pub fn delete(&self) {
- Application::default()
- .session_list()
- .settings()
- .remove(&self.session_id());
+ pub(crate) fn delete(&self) {
+ session_list_settings().remove(&self.session_id());
}
/// Custom servers to explore.
@@ -189,7 +199,7 @@ impl SessionSettings {
}
/// Set the custom servers to explore.
- pub fn set_explore_custom_servers(&self, servers: IndexSet) {
+ pub(crate) fn set_explore_custom_servers(&self, servers: IndexSet) {
if self.explore_custom_servers() == servers {
return;
}
@@ -198,11 +208,11 @@ impl SessionSettings {
.stored_settings
.borrow_mut()
.explore_custom_servers = servers;
- Self::save();
+ session_list_settings().save();
}
/// Whether the section with the given name is expanded.
- pub fn is_section_expanded(&self, section_name: SidebarSectionName) -> bool {
+ pub(crate) fn is_section_expanded(&self, section_name: SidebarSectionName) -> bool {
self.imp()
.stored_settings
.borrow()
@@ -211,28 +221,75 @@ impl SessionSettings {
}
/// Set whether the section with the given name is expanded.
- pub fn set_section_expanded(&self, section_name: SidebarSectionName, expanded: bool) {
+ pub(crate) fn set_section_expanded(&self, section_name: SidebarSectionName, expanded: bool) {
self.imp()
.stored_settings
.borrow_mut()
.sections_expanded
.set_section_expanded(section_name, expanded);
- Self::save();
+ 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(
+ &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.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
-pub struct SectionsExpanded(BTreeSet);
+pub(crate) struct SectionsExpanded(BTreeSet);
impl SectionsExpanded {
/// Whether the section with the given name is expanded.
- pub fn is_section_expanded(&self, section_name: SidebarSectionName) -> bool {
+ pub(crate) fn is_section_expanded(&self, section_name: SidebarSectionName) -> bool {
self.0.contains(§ion_name)
}
/// Set whether the section with the given name is expanded.
- pub fn set_section_expanded(&mut self, section_name: SidebarSectionName, expanded: bool) {
+ pub(crate) fn set_section_expanded(
+ &mut self,
+ section_name: SidebarSectionName,
+ expanded: bool,
+ ) {
if expanded {
self.0.insert(section_name);
} else {
@@ -252,3 +309,69 @@ impl Default for SectionsExpanded {
]))
}
}
+
+/// Setting about which rooms display media previews.
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub(crate) 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)
+ }
+}
+
+/// 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 {
+ /// All rooms show media previews.
+ All,
+ /// Only private rooms show media previews.
+ #[default]
+ Private,
+ /// No rooms show media previews.
+ 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(deserializer: D) -> Result
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let cow = ruma::serde::deserialize_cow_str(deserializer)?;
+ cow.parse().map_err(serde::de::Error::custom)
+ }
+}
+
+/// The session list settings of the application.
+fn session_list_settings() -> SessionListSettings {
+ Application::default().session_list().settings()
+}
diff --git a/src/session/view/account_settings/safety_page/mod.rs b/src/session/view/account_settings/safety_page/mod.rs
index da7c0e34..6670177d 100644
--- a/src/session/view/account_settings/safety_page/mod.rs
+++ b/src/session/view/account_settings/safety_page/mod.rs
@@ -1,13 +1,17 @@
use adw::{prelude::*, subclass::prelude::*};
use gtk::{glib, glib::clone, CompositeTemplate};
+use tracing::error;
mod ignored_users_subpage;
pub(super) use self::ignored_users_subpage::IgnoredUsersSubpage;
-use crate::{components::ButtonCountRow, session::model::Session};
+use crate::{
+ components::ButtonCountRow,
+ session::model::{MediaPreviewsGlobalSetting, Session},
+};
mod imp {
- use std::cell::RefCell;
+ use std::{cell::RefCell, marker::PhantomData};
use glib::subclass::InitializingObject;
@@ -26,7 +30,11 @@ mod imp {
/// The current session.
#[property(get, set = Self::set_session, nullable)]
session: glib::WeakRef,
+ /// The media previews setting, as a string.
+ #[property(get = Self::media_previews_enabled, set = Self::set_media_previews_enabled)]
+ media_previews_enabled: PhantomData,
ignored_users_count_handler: RefCell