Browse Source

Upgrade ruma and matrix-sdk

And add support for room version 12.
fractal-12
Kévin Commaille 8 months ago
parent
commit
8cfc2e1ac7
No known key found for this signature in database
GPG Key ID: F26F4BE20A08255B
  1. 34
      Cargo.lock
  2. 9
      Cargo.toml
  3. 17
      data/resources/stylesheet/_components.scss
  4. 12
      src/components/power_level_selection/combo_box.rs
  5. 66
      src/components/power_level_selection/popover.rs
  6. 2
      src/components/power_level_selection/popover.ui
  7. 93
      src/components/power_level_selection/row.rs
  8. 4
      src/components/power_level_selection/row.ui
  9. 6
      src/components/role_badge.rs
  10. 22
      src/components/user_page.rs
  11. 2
      src/components/user_page.ui
  12. 2
      src/login/mod.rs
  13. 102
      src/session/model/room/member.rs
  14. 2
      src/session/model/room/member_list.rs
  15. 6
      src/session/model/room/mod.rs
  16. 159
      src/session/model/room/permissions.rs
  17. 2
      src/session/view/content/room_details/members_page/members_list_view/mod.rs
  18. 13
      src/session/view/content/room_details/permissions/add_members_subpage.rs
  19. 114
      src/session/view/content/room_details/permissions/member_power_level.rs
  20. 39
      src/session/view/content/room_details/permissions/member_row.rs
  21. 5
      src/session/view/content/room_details/permissions/member_row.ui
  22. 2
      src/session/view/content/room_details/permissions/members_subpage.rs
  23. 111
      src/session/view/content/room_details/permissions/permissions_subpage.rs
  24. 34
      src/session/view/content/room_details/permissions/permissions_subpage.ui
  25. 6
      src/session/view/content/room_details/permissions/privileged_members.rs
  26. 24
      src/session/view/content/room_history/message_toolbar/mod.rs
  27. 18
      src/session/view/content/room_history/sender_avatar/mod.rs

34
Cargo.lock generated

@ -3028,7 +3028,7 @@ dependencies = [
[[package]]
name = "matrix-sdk"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"anymap2",
"aquamarine",
@ -3085,7 +3085,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-base"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"as_variant",
"async-trait",
@ -3112,7 +3112,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-common"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"eyeball-im",
"futures-core",
@ -3135,7 +3135,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-crypto"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"aes",
"aquamarine",
@ -3176,7 +3176,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-indexeddb"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"anyhow",
"async-trait",
@ -3204,7 +3204,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-qrcode"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"byteorder",
"qrcode",
@ -3216,7 +3216,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-sqlite"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"as_variant",
"async-trait",
@ -3241,7 +3241,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-store-encryption"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"base64",
"blake3",
@ -3260,7 +3260,7 @@ dependencies = [
[[package]]
name = "matrix-sdk-ui"
version = "0.13.0"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=93e25496adac5dfcd15e938438511e87fe63fcd3#93e25496adac5dfcd15e938438511e87fe63fcd3"
source = "git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=ada68e11144507afc9d178f4264452aae1ff9e27#ada68e11144507afc9d178f4264452aae1ff9e27"
dependencies = [
"as_variant",
"async-rx",
@ -4230,7 +4230,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.12.5"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"assign",
"js_int",
@ -4246,7 +4246,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.20.4"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"as_variant",
"assign",
@ -4269,7 +4269,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.15.4"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"as_variant",
"base64",
@ -4302,7 +4302,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.30.4"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"as_variant",
"indexmap",
@ -4328,7 +4328,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.11.2"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"headers",
"http",
@ -4346,7 +4346,7 @@ dependencies = [
[[package]]
name = "ruma-html"
version = "0.4.1"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"as_variant",
"html5ever",
@ -4358,7 +4358,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.10.1"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"js_int",
"thiserror 2.0.12",
@ -4367,7 +4367,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.15.2"
source = "git+https://github.com/ruma/ruma.git?rev=a3663c04511f79f99376924d739f84d839600de6#a3663c04511f79f99376924d739f84d839600de6"
source = "git+https://github.com/ruma/ruma.git?rev=de19ebaf71af620eb17abaefd92e43153f9d041d#de19ebaf71af620eb17abaefd92e43153f9d041d"
dependencies = [
"cfg-if",
"proc-macro-crate",

9
Cargo.toml

@ -73,23 +73,23 @@ sourceview = { package = "sourceview5", version = "0.9" }
[dependencies.matrix-sdk]
# version = "0.13"
git = "https://github.com/matrix-org/matrix-rust-sdk.git"
rev = "93e25496adac5dfcd15e938438511e87fe63fcd3"
rev = "ada68e11144507afc9d178f4264452aae1ff9e27"
features = ["socks", "sso-login", "markdown", "qrcode"]
[dependencies.matrix-sdk-store-encryption]
# version = "0.13"
git = "https://github.com/matrix-org/matrix-rust-sdk.git"
rev = "93e25496adac5dfcd15e938438511e87fe63fcd3"
rev = "ada68e11144507afc9d178f4264452aae1ff9e27"
[dependencies.matrix-sdk-ui]
# version = "0.13"
git = "https://github.com/matrix-org/matrix-rust-sdk.git"
rev = "93e25496adac5dfcd15e938438511e87fe63fcd3"
rev = "ada68e11144507afc9d178f4264452aae1ff9e27"
[dependencies.ruma]
# version = "0.12.5"
git = "https://github.com/ruma/ruma.git"
rev = "a3663c04511f79f99376924d739f84d839600de6"
rev = "de19ebaf71af620eb17abaefd92e43153f9d041d"
features = [
"client-api-c",
"markdown",
@ -102,7 +102,6 @@ features = [
"compat-unset-avatar",
"compat-lax-room-create-deser",
"compat-lax-room-topic-deser",
"unstable-msc3824",
]
# Linux-only dependencies.

17
data/resources/stylesheet/_components.scss

@ -6,7 +6,7 @@
inline-pill {
border-radius: 9999px;
background-color: vendor.$button_color;
@include vendor.focus-ring();
&.activatable {
@ -46,14 +46,19 @@ role-badge {
padding: 0.1em 0.5em;
font-size: 0.8em;
&.creator {
color: var(--accent-fg-color);
background-color: var(--accent-purple);
}
&.admin {
color: var(--error-fg-color);
background-color: var(--error-bg-color);
color: var(--accent-fg-color);
background-color: var(--accent-red);
}
&.mod {
color: var(--warning-fg-color);
background-color: var(--warning-bg-color);
color: var(--accent-fg-color);
background-color: var(--accent-yellow);
}
&.muted {
@ -160,4 +165,4 @@ crop-circle > .mask {
user-page scrolledwindow > viewport > clamp > box {
margin: 12px;
border-spacing: 24px;
}
}

12
src/components/power_level_selection/combo_box.rs

@ -1,11 +1,9 @@
use adw::{prelude::*, subclass::prelude::*};
use gtk::{CompositeTemplate, gdk, glib};
use ruma::{Int, events::room::power_levels::UserPowerLevel};
use super::PowerLevelSelectionPopover;
use crate::{
components::RoleBadge,
session::model::{Permissions, PowerLevel},
};
use crate::{components::RoleBadge, session::model::Permissions};
mod imp {
use std::cell::{Cell, RefCell};
@ -29,7 +27,7 @@ mod imp {
permissions: RefCell<Option<Permissions>>,
/// The selected power level.
#[property(get, set = Self::set_selected_power_level, explicit_notify)]
selected_power_level: Cell<PowerLevel>,
selected_power_level: Cell<i64>,
}
#[glib::object_subclass]
@ -75,7 +73,7 @@ mod imp {
};
let power_level = self.selected_power_level.get();
let role = permissions.role(power_level);
let role = permissions.role(UserPowerLevel::Int(Int::new_saturating(power_level)));
self.selected_role_badge.set_role(role);
self.selected_level_label
@ -87,7 +85,7 @@ mod imp {
}
/// Set the selected power level.
fn set_selected_power_level(&self, power_level: PowerLevel) {
fn set_selected_power_level(&self, power_level: i64) {
if self.selected_power_level.get() == power_level {
return;
}

66
src/components/power_level_selection/popover.rs

@ -1,8 +1,9 @@
use adw::{prelude::*, subclass::prelude::*};
use gtk::{CompositeTemplate, glib, glib::clone};
use ruma::events::room::power_levels::UserPowerLevel;
use crate::{
session::model::{POWER_LEVEL_ADMIN, POWER_LEVEL_MOD, Permissions, PowerLevel},
session::model::{POWER_LEVEL_ADMIN, POWER_LEVEL_MAX, POWER_LEVEL_MOD, Permissions},
utils::BoundObject,
};
@ -48,7 +49,7 @@ mod imp {
permissions: BoundObject<Permissions>,
/// The selected power level.
#[property(get, set = Self::set_selected_power_level, explicit_notify)]
selected_power_level: Cell<PowerLevel>,
selected_power_level: Cell<i64>,
}
#[glib::object_subclass]
@ -84,7 +85,7 @@ mod imp {
self.permissions.disconnect_signals();
if let Some(permissions) = permissions {
let own_pl_handler = permissions.connect_own_power_level_notify(clone!(
let own_pl_handler = permissions.connect_own_power_level_changed(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
@ -120,7 +121,7 @@ mod imp {
}
/// Set the selected power level.
fn set_selected_power_level(&self, power_level: PowerLevel) {
fn set_selected_power_level(&self, power_level: i64) {
if self.selected_power_level.get() == power_level {
return;
}
@ -148,15 +149,10 @@ mod imp {
return;
};
let can_change_to_admin = permissions.own_power_level() >= POWER_LEVEL_ADMIN;
let can_change_to_admin = permissions.can_set_user_power_level_to(POWER_LEVEL_ADMIN);
if can_change_to_admin {
self.admin_row.set_sensitive(true);
self.admin_row.set_activatable(true);
} else {
self.admin_row.set_sensitive(false);
self.admin_row.set_activatable(false);
}
self.admin_row.set_sensitive(can_change_to_admin);
self.admin_row.set_activatable(can_change_to_admin);
}
/// Update the moderator row.
@ -165,15 +161,10 @@ mod imp {
return;
};
let can_change_to_mod = permissions.own_power_level() >= POWER_LEVEL_MOD;
let can_change_to_mod = permissions.can_set_user_power_level_to(POWER_LEVEL_MOD);
if can_change_to_mod {
self.mod_row.set_sensitive(true);
self.mod_row.set_activatable(true);
} else {
self.mod_row.set_sensitive(false);
self.mod_row.set_activatable(false);
}
self.mod_row.set_sensitive(can_change_to_mod);
self.mod_row.set_activatable(can_change_to_mod);
}
/// Update the default row.
@ -185,15 +176,10 @@ mod imp {
let default = permissions.default_power_level();
self.default_pl_label.set_label(&default.to_string());
let can_change_to_default = permissions.own_power_level() >= default;
let can_change_to_default = permissions.can_set_user_power_level_to(default);
if can_change_to_default {
self.default_row.set_sensitive(true);
self.default_row.set_activatable(true);
} else {
self.default_row.set_sensitive(false);
self.default_row.set_activatable(false);
}
self.default_row.set_sensitive(can_change_to_default);
self.default_row.set_activatable(can_change_to_default);
}
/// Update the muted row.
@ -214,15 +200,10 @@ mod imp {
self.muted_pl_label.set_label(&mute.to_string());
let can_change_to_muted = permissions.own_power_level() >= mute;
let can_change_to_muted = permissions.can_set_user_power_level_to(mute);
if can_change_to_muted {
self.muted_row.set_sensitive(true);
self.muted_row.set_activatable(true);
} else {
self.muted_row.set_sensitive(false);
self.muted_row.set_activatable(false);
}
self.muted_row.set_sensitive(can_change_to_muted);
self.muted_row.set_activatable(can_change_to_muted);
self.muted_row.set_visible(true);
}
@ -233,8 +214,13 @@ mod imp {
return;
};
self.custom_adjustment
.set_upper(permissions.own_power_level() as f64);
let max = if let UserPowerLevel::Int(value) = permissions.own_power_level() {
i64::from(value)
} else {
POWER_LEVEL_MAX
};
self.custom_adjustment.set_upper(max as f64);
self.custom_adjustment
.set_value(self.selected_power_level.get() as f64);
}
@ -260,7 +246,7 @@ mod imp {
/// The custom value changed.
#[template_callback]
fn custom_value_changed(&self) {
let power_level = self.custom_adjustment.value() as PowerLevel;
let power_level = self.custom_adjustment.value() as i64;
let can_confirm = power_level != self.selected_power_level.get();
self.custom_confirm.set_sensitive(can_confirm);
@ -269,7 +255,7 @@ mod imp {
/// The custom value was confirmed.
#[template_callback]
fn custom_value_confirmed(&self) {
let power_level = self.custom_adjustment.value() as PowerLevel;
let power_level = self.custom_adjustment.value() as i64;
self.obj().popdown();
self.set_selected_power_level(power_level);

2
src/components/power_level_selection/popover.ui

@ -181,4 +181,4 @@
</object>
</property>
</template>
</interface>
</interface>

93
src/components/power_level_selection/row.rs

@ -1,23 +1,25 @@
use adw::{prelude::*, subclass::prelude::*};
use gtk::{CompositeTemplate, glib};
use gtk::{CompositeTemplate, glib, glib::closure_local};
use ruma::{Int, events::room::power_levels::UserPowerLevel, int};
use super::PowerLevelSelectionPopover;
use crate::{
components::{LoadingBin, RoleBadge},
session::model::{Permissions, PowerLevel},
session::model::Permissions,
};
mod imp {
use std::{
cell::{Cell, RefCell},
marker::PhantomData,
sync::LazyLock,
};
use glib::subclass::InitializingObject;
use glib::subclass::{InitializingObject, Signal};
use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[derive(Debug, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/components/power_level_selection/row.ui")]
#[properties(wrapper_type = super::PowerLevelSelectionRow)]
pub struct PowerLevelSelectionRow {
@ -41,8 +43,7 @@ mod imp {
#[property(get, set = Self::set_permissions, explicit_notify, nullable)]
permissions: RefCell<Option<Permissions>>,
/// The selected power level.
#[property(get, set = Self::set_selected_power_level, explicit_notify)]
selected_power_level: Cell<PowerLevel>,
pub(super) selected_power_level: Cell<UserPowerLevel>,
/// Whether the selected power level should be displayed in the
/// subtitle, rather than next to the combo arrow.
#[property(get, set = Self::set_use_subtitle, explicit_notify)]
@ -55,6 +56,26 @@ mod imp {
read_only: Cell<bool>,
}
impl Default for PowerLevelSelectionRow {
fn default() -> Self {
Self {
subtitle_bin: Default::default(),
combo_selection_bin: Default::default(),
arrow_box: Default::default(),
loading_bin: Default::default(),
popover: Default::default(),
selected_box: Default::default(),
selected_level_label: Default::default(),
selected_role_badge: Default::default(),
permissions: Default::default(),
selected_power_level: Cell::new(UserPowerLevel::Int(int!(0))),
use_subtitle: Default::default(),
is_loading: PhantomData,
read_only: Default::default(),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for PowerLevelSelectionRow {
const NAME: &'static str = "PowerLevelSelectionRow";
@ -81,6 +102,12 @@ mod imp {
#[glib::derived_properties]
impl ObjectImpl for PowerLevelSelectionRow {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> =
LazyLock::new(|| vec![Signal::builder("selected-power-level-changed").build()]);
SIGNALS.as_ref()
}
fn constructed(&self) {
self.parent_constructed();
@ -110,21 +137,28 @@ mod imp {
let Some(permissions) = self.permissions.borrow().clone() else {
return;
};
let obj = self.obj();
let power_level = self.selected_power_level.get();
let role = permissions.role(power_level);
self.selected_role_badge.set_role(role);
self.selected_level_label
.set_label(&power_level.to_string());
let role_string = format!("{power_level} {role}");
obj.update_property(&[gtk::accessible::Property::Description(&role_string)]);
let (level_visible, accessible_desc) = if let UserPowerLevel::Int(value) = power_level {
self.selected_level_label.set_label(&value.to_string());
self.popover.set_selected_power_level(i64::from(value));
(true, format!("{value} {role}"))
} else {
(false, role.to_string())
};
self.selected_level_label.set_visible(level_visible);
self.obj()
.update_property(&[gtk::accessible::Property::Description(&accessible_desc)]);
}
/// Set the selected power level.
fn set_selected_power_level(&self, power_level: PowerLevel) {
pub(super) fn set_selected_power_level(&self, power_level: UserPowerLevel) {
if self.selected_power_level.get() == power_level {
return;
}
@ -132,7 +166,8 @@ mod imp {
self.selected_power_level.set(power_level);
self.update_selected_label();
self.obj().notify_selected_power_level();
self.obj()
.emit_by_name::<()>("selected-power-level-changed", &[]);
}
/// Set whether the selected power level should be displayed in the
@ -216,6 +251,14 @@ mod imp {
obj.remove_css_class("has-open-popup");
}
}
/// The selected power level changed.
#[template_callback]
fn power_level_changed(&self) {
self.set_selected_power_level(UserPowerLevel::Int(Int::new_saturating(
self.popover.selected_power_level(),
)));
}
}
}
@ -230,4 +273,28 @@ impl PowerLevelSelectionRow {
pub fn new() -> Self {
glib::Object::new()
}
/// The selected power level.
pub(crate) fn selected_power_level(&self) -> UserPowerLevel {
self.imp().selected_power_level.get()
}
/// Set the selected power level.
pub(crate) fn set_selected_power_level(&self, power_level: UserPowerLevel) {
self.imp().set_selected_power_level(power_level);
}
/// Connect to the signal emitted when the selected power level changed.
pub fn connect_power_level_changed<F: Fn(&Self) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_closure(
"selected-power-level-changed",
true,
closure_local!(move |obj: Self| {
f(&obj);
}),
)
}
}

4
src/components/power_level_selection/row.ui

@ -77,8 +77,8 @@
<child>
<object class="PowerLevelSelectionPopover" id="popover">
<signal name="notify::visible" handler="popover_visible" swapped="true" />
<signal name="notify::selected-power-level" handler="power_level_changed" swapped="true" />
<property name="permissions" bind-source="PowerLevelSelectionRow" bind-property="permissions" bind-flags="sync-create"/>
<property name="selected-power-level" bind-source="PowerLevelSelectionRow" bind-property="selected-power-level" bind-flags="sync-create | bidirectional"/>
</object>
</child>
</object>
@ -101,4 +101,4 @@
</object>
</child>
</object>
</interface>
</interface>

6
src/components/role_badge.rs

@ -77,6 +77,12 @@ mod imp {
self.label.set_text(&role.to_string());
if role == MemberRole::Creator {
obj.add_css_class("creator");
} else {
obj.remove_css_class("creator");
}
if role == MemberRole::Administrator {
obj.add_css_class("admin");
} else {

22
src/components/user_page.rs

@ -6,7 +6,10 @@ use gtk::{
CompositeTemplate, glib,
glib::{clone, closure_local},
};
use ruma::{OwnedEventId, events::room::power_levels::PowerLevelUserAction};
use ruma::{
OwnedEventId,
events::room::power_levels::{PowerLevelUserAction, UserPowerLevel},
};
use super::{Avatar, LoadingButton, LoadingButtonRow, PowerLevelSelectionRow};
use crate::{
@ -194,7 +197,7 @@ mod imp {
}
}
));
let power_level_handler = member.connect_power_level_notify(clone!(
let power_level_handler = member.connect_power_level_changed(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
@ -433,8 +436,15 @@ mod imp {
};
let row = &self.power_level_row;
let power_level = row.selected_power_level();
let old_power_level = member.power_level();
let UserPowerLevel::Int(power_level) = row.selected_power_level() else {
// We cannot set the power level to infinite.
return;
};
let UserPowerLevel::Int(old_power_level) = member.power_level() else {
// We cannot change the power level if it is currently infinite.
return;
};
if old_power_level == power_level {
// Nothing to do.
@ -456,8 +466,8 @@ mod imp {
} else {
// Warn if user is muted but was not before.
let mute_power_level = permissions.mute_power_level();
let is_muted =
power_level <= mute_power_level && old_power_level > mute_power_level;
let is_muted = i64::from(power_level) <= mute_power_level
&& i64::from(old_power_level) > mute_power_level;
if is_muted
&& !confirm_mute_room_member_dialog(slice::from_ref(&member), &*obj).await
{

2
src/components/user_page.ui

@ -130,7 +130,7 @@
<child>
<object class="PowerLevelSelectionRow" id="power_level_row">
<property name="title" translatable="yes" comments="Translators: value used to assign room member roles">Power Level</property>
<signal name="notify::selected-power-level" handler="set_power_level" swapped="yes" />
<signal name="selected-power-level-changed" handler="set_power_level" swapped="yes" />
</object>
</child>
<child>

2
src/login/mod.rs

@ -288,7 +288,7 @@ mod imp {
let oauth = client.oauth();
let handle = spawn_tokio!(async move {
oauth
.login(redirect_uri, None, Some(client_registration_data()))
.login(redirect_uri, None, Some(client_registration_data()), None)
.build()
.await
});

102
src/session/model/room/member.rs

@ -1,18 +1,21 @@
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use gtk::{
glib,
glib::{clone, closure_local},
prelude::*,
subclass::prelude::*,
};
use matrix_sdk::room::RoomMember;
use ruma::{
OwnedEventId, OwnedUserId,
events::room::{
member::MembershipState,
power_levels::{NotificationPowerLevelType, PowerLevelAction},
power_levels::{NotificationPowerLevelType, PowerLevelAction, UserPowerLevel},
},
int,
};
use tracing::{debug, error};
use super::{
MemberRole, Room,
permissions::{POWER_LEVEL_MAX, POWER_LEVEL_MIN, PowerLevel},
};
use super::{MemberRole, Room};
use crate::{components::PillSource, prelude::*, session::model::User, spawn, spawn_tokio};
/// The possible states of membership of a user in a room.
@ -54,19 +57,32 @@ impl From<MembershipState> for Membership {
}
mod imp {
use std::cell::{Cell, OnceCell, RefCell};
use std::{
cell::{Cell, OnceCell, RefCell},
marker::PhantomData,
sync::LazyLock,
};
use glib::subclass::Signal;
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[derive(Debug, glib::Properties)]
#[properties(wrapper_type = super::Member)]
pub struct Member {
/// The room of the member.
#[property(get, set = Self::set_room, construct_only)]
room: OnceCell<Room>,
/// The power level of the member.
#[property(get, minimum = POWER_LEVEL_MIN, maximum = POWER_LEVEL_MAX)]
power_level: Cell<PowerLevel>,
pub(super) power_level: Cell<UserPowerLevel>,
/// The power level of the member, as an `i64`.
///
/// Should only be used for sorting.
///
/// `i64::MAX` is used to represent an infinite power level, since it
/// cannot be reached with the Matrix specification.
#[property(get = Self::power_level_i64)]
power_level_i64: PhantomData<i64>,
/// The role of the member.
#[property(get, builder(MemberRole::default()))]
role: Cell<MemberRole>,
@ -79,6 +95,20 @@ mod imp {
power_level_handlers: RefCell<Vec<glib::SignalHandlerId>>,
}
impl Default for Member {
fn default() -> Self {
Self {
room: Default::default(),
power_level: Cell::new(UserPowerLevel::Int(int!(0))),
power_level_i64: Default::default(),
role: Default::default(),
membership: Default::default(),
latest_activity: Default::default(),
power_level_handlers: Default::default(),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for Member {
const NAME: &'static str = "Member";
@ -88,6 +118,12 @@ mod imp {
#[glib::derived_properties]
impl ObjectImpl for Member {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> =
LazyLock::new(|| vec![Signal::builder("power-level-changed").build()]);
SIGNALS.as_ref()
}
fn dispose(&self) {
if let Some(room) = self.room.get() {
for handler in self.power_level_handlers.take() {
@ -129,14 +165,28 @@ mod imp {
}
/// Set the power level of the member.
pub(super) fn set_power_level(&self, power_level: PowerLevel) {
pub(super) fn set_power_level(&self, power_level: UserPowerLevel) {
if self.power_level.get() == power_level {
return;
}
self.power_level.set(power_level);
self.update_role();
self.obj().notify_power_level();
let obj = self.obj();
obj.emit_by_name::<()>("power-level-changed", &[]);
obj.notify_power_level_i64();
}
/// The power level of the member, as an `i64`.
fn power_level_i64(&self) -> i64 {
if let UserPowerLevel::Int(power_level) = self.power_level.get() {
power_level.into()
} else {
// Represent the infinite power level with a value out of range for a power
// level.
i64::MAX
}
}
/// Update the role of the member.
@ -195,8 +245,13 @@ impl Member {
obj
}
/// The power level of the member.
pub(crate) fn power_level(&self) -> UserPowerLevel {
self.imp().power_level.get()
}
/// Set the power level of the member.
pub(super) fn set_power_level(&self, power_level: PowerLevel) {
pub(super) fn set_power_level(&self, power_level: UserPowerLevel) {
self.imp().set_power_level(power_level);
}
@ -268,12 +323,21 @@ impl Member {
/// The string to use to search for this member.
pub(crate) fn search_string(&self) -> String {
format!(
"{} {} {} {}",
self.display_name(),
self.user_id(),
self.role(),
self.power_level(),
format!("{} {} {}", self.display_name(), self.user_id(), self.role())
}
/// Connect to the signal emitted when the power level of the member
/// changed.
pub(crate) fn connect_power_level_changed<F: Fn(&Self) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_closure(
"power-level-changed",
true,
closure_local!(move |obj: Self| {
f(&obj);
}),
)
}
}

2
src/session/model/room/member_list.rs

@ -318,7 +318,7 @@ impl MemberList {
// We need to go through the whole list because we don't know who was
// added/removed.
for (user_id, member) in &*self.imp().members.borrow() {
member.set_power_level(power_levels.for_user(user_id).into());
member.set_power_level(power_levels.for_user(user_id));
}
}
}

6
src/session/model/room/mod.rs

@ -867,9 +867,9 @@ mod imp {
let member_event = match raw_member_event {
RawSyncOrStrippedState::Sync(raw) => {
raw.deserialize_as::<RoomMemberMembershipEvent>()
raw.deserialize_as_unchecked::<RoomMemberMembershipEvent>()
}
RawSyncOrStrippedState::Stripped(raw) => raw.deserialize_as(),
RawSyncOrStrippedState::Stripped(raw) => raw.deserialize_as_unchecked(),
};
let member_event = match member_event {
@ -927,7 +927,7 @@ mod imp {
match raw_prev_member_event
.kind
.raw()
.deserialize_as::<RoomMemberMembershipEvent>()
.deserialize_as_unchecked::<RoomMemberMembershipEvent>()
{
Ok(prev_member_event) => {
prev_member_event.content.membership == MembershipState::Invite

159
src/session/model/room/permissions.rs

@ -14,36 +14,28 @@ use ruma::{
MessageLikeEventType, StateEventType, SyncStateEvent,
room::power_levels::{
NotificationPowerLevelType, PowerLevelAction, PowerLevelUserAction, RoomPowerLevels,
RoomPowerLevelsEventContent,
RoomPowerLevelsEventContent, RoomPowerLevelsSource, UserPowerLevel,
},
},
int,
room_version_rules::AuthorizationRules,
};
use tracing::error;
use super::{Member, Membership, Room};
use crate::{prelude::*, spawn, spawn_tokio};
/// Power level of a user.
///
/// Is usually in the range (0..=100), but can be any JS integer.
pub type PowerLevel = i64;
/// The maximum power level that can be set, according to the Matrix
/// specification.
///
/// This is the same value as `MAX_SAFE_INT` from the `js_int` crate.
pub const POWER_LEVEL_MAX: PowerLevel = 0x001F_FFFF_FFFF_FFFF;
/// The minimum power level that can be set, according to the Matrix
/// specification.
///
/// This is the same value as `MIN_SAFE_INT` from the `js_int` crate.
pub const POWER_LEVEL_MIN: PowerLevel = -POWER_LEVEL_MAX;
pub const POWER_LEVEL_MAX: i64 = 0x001F_FFFF_FFFF_FFFF;
/// The minimum power level to have the role of Administrator, according to the
/// Matrix specification.
pub const POWER_LEVEL_ADMIN: PowerLevel = 100;
pub const POWER_LEVEL_ADMIN: i64 = 100;
/// The minimum power level to have the role of Moderator, according to the
/// Matrix specification.
pub const POWER_LEVEL_MOD: PowerLevel = 50;
pub const POWER_LEVEL_MOD: i64 = 50;
/// Role of a room member, like admin or moderator.
#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
@ -59,6 +51,8 @@ pub enum MemberRole {
Moderator,
/// An administrator.
Administrator,
/// A creator.
Creator,
/// A room member that cannot send messages.
Muted,
}
@ -72,6 +66,7 @@ impl fmt::Display for MemberRole {
Self::Custom => write!(f, "{}", gettext("Custom")),
Self::Moderator => write!(f, "{}", gettext("Moderator")),
Self::Administrator => write!(f, "{}", gettext("Admin")),
Self::Creator => write!(f, "{}", gettext("Creator")),
// Translators: As in 'Muted room member', a member that cannot send messages.
Self::Muted => write!(f, "{}", gettext("Muted")),
}
@ -101,14 +96,13 @@ mod imp {
#[property(get)]
is_joined: Cell<bool>,
/// The power level of our own member.
#[property(get)]
own_power_level: Cell<PowerLevel>,
pub(super) own_power_level: Cell<UserPowerLevel>,
/// The default power level for members.
#[property(get)]
default_power_level: Cell<PowerLevel>,
default_power_level: Cell<i64>,
/// The power level to mute members.
#[property(get)]
mute_power_level: Cell<PowerLevel>,
mute_power_level: Cell<i64>,
/// Whether our own member can change the room's avatar.
#[property(get)]
can_change_avatar: Cell<bool>,
@ -142,10 +136,14 @@ mod imp {
fn default() -> Self {
Self {
room: Default::default(),
power_levels: RefCell::new(RoomPowerLevelsEventContent::default().into()),
power_levels: RefCell::new(RoomPowerLevels::new(
RoomPowerLevelsSource::None,
&AuthorizationRules::V1,
None,
)),
power_levels_drop_guard: Default::default(),
is_joined: Default::default(),
own_power_level: Default::default(),
own_power_level: Cell::new(UserPowerLevel::Int(int!(0))),
default_power_level: Default::default(),
mute_power_level: Default::default(),
can_change_avatar: Default::default(),
@ -170,8 +168,12 @@ mod imp {
#[glib::derived_properties]
impl ObjectImpl for Permissions {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> =
LazyLock::new(|| vec![Signal::builder("changed").build()]);
static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
vec![
Signal::builder("changed").build(),
Signal::builder("own-power-level-changed").build(),
]
});
SIGNALS.as_ref()
}
}
@ -206,27 +208,19 @@ mod imp {
// We will probably not be able to load the power levels if we were never in the
// room, so skip this. We should get the power levels when we join the room.
if !matches!(matrix_room.state(), RoomState::Invited | RoomState::Knocked) {
let matrix_room_clone = matrix_room.clone();
let handle = spawn_tokio!(async move { matrix_room_clone.power_levels().await });
match handle.await.expect("task was not aborted") {
Ok(power_levels) => self.update_power_levels(&power_levels),
Err(error) => {
error!("Could not load room power levels: {error}");
}
}
self.update_power_levels().await;
}
let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
let handle = matrix_room.add_event_handler(
move |event: SyncStateEvent<RoomPowerLevelsEventContent>| {
move |_event: SyncStateEvent<RoomPowerLevelsEventContent>| {
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_power_levels(&event.power_levels());
obj.imp().update_power_levels().await;
}
});
});
@ -256,19 +250,32 @@ mod imp {
self.permissions_changed();
}
/// Update the power levels with the given data.
fn update_power_levels(&self, power_levels: &RoomPowerLevels) {
/// Update the power levels with the data from the SDK's room.
async fn update_power_levels(&self) {
let Some(room) = self.room.upgrade() else {
return;
};
let matrix_room = room.matrix_room().clone();
let handle = spawn_tokio!(async move { matrix_room.power_levels().await });
let power_levels = match handle.await.expect("task was not aborted") {
Ok(power_levels) => power_levels,
Err(error) => {
error!("Could not load room power levels: {error}");
return;
}
};
self.power_levels.replace(power_levels.clone());
self.permissions_changed();
if let Some(room) = self.room.upgrade() {
if let Some(members) = room.members() {
members.update_power_levels(power_levels);
} else {
let own_member = room.own_member();
let own_user_id = own_member.user_id();
own_member.set_power_level(power_levels.for_user(own_user_id).into());
}
if let Some(members) = room.members() {
members.update_power_levels(&power_levels);
} else {
let own_member = room.own_member();
let own_user_id = own_member.user_id();
own_member.set_power_level(power_levels.for_user(own_user_id));
}
}
@ -296,18 +303,15 @@ mod imp {
};
let own_member = room.own_member();
let power_level = self
.power_levels
.borrow()
.for_user(own_member.user_id())
.into();
let power_level = self.power_levels.borrow().for_user(own_member.user_id());
if self.own_power_level.get() == power_level {
return;
}
self.own_power_level.set(power_level);
self.obj().notify_own_power_level();
self.obj()
.emit_by_name::<()>("own-power-level-changed", &[]);
}
/// Update the default power level for members.
@ -500,13 +504,24 @@ impl Permissions {
self.imp().power_levels.borrow().clone()
}
/// The power level of our own member.
pub(crate) fn own_power_level(&self) -> UserPowerLevel {
self.imp().own_power_level.get()
}
/// The power level for the user with the given ID.
pub(crate) fn user_power_level(&self, user_id: &UserId) -> PowerLevel {
self.imp().power_levels.borrow().for_user(user_id).into()
pub(crate) fn user_power_level(&self, user_id: &UserId) -> UserPowerLevel {
self.imp().power_levels.borrow().for_user(user_id)
}
/// The current [`MemberRole`] for the given power level.
pub(crate) fn role(&self, power_level: PowerLevel) -> MemberRole {
pub(crate) fn role(&self, power_level: UserPowerLevel) -> MemberRole {
let UserPowerLevel::Int(power_level) = power_level else {
return MemberRole::Creator;
};
let power_level = i64::from(power_level);
if power_level >= POWER_LEVEL_ADMIN {
MemberRole::Administrator
} else if power_level >= POWER_LEVEL_MOD {
@ -546,19 +561,26 @@ impl Permissions {
let power_levels = imp.power_levels.borrow();
if own_user_id == user_id {
// The only action we can do for our own user is change the power level.
// The only action we can do for our own user is change the power level, if it's
// not a creator.
return action == PowerLevelUserAction::ChangePowerLevel
&& power_levels.user_can_send_state(own_user_id, StateEventType::RoomPowerLevels);
&& power_levels.user_can_change_user_power_level(own_user_id, own_user_id);
}
power_levels.user_can_do_to_user(own_user_id, user_id, action)
}
/// Whether our user can set the given power level for another user.
pub(crate) fn can_set_user_power_level_to(&self, power_level: i64) -> bool {
self.is_allowed_to(PowerLevelAction::SendState(StateEventType::RoomPowerLevels))
&& self.own_power_level() >= Int::new_saturating(power_level)
}
/// Set the power level of the room member with the given user ID.
pub(crate) async fn set_user_power_level(
&self,
user_id: OwnedUserId,
power_level: PowerLevel,
power_level: Int,
) -> Result<(), ()> {
let Some(room) = self.room() else {
return Err(());
@ -566,7 +588,6 @@ impl Permissions {
let matrix_room = room.matrix_room().clone();
let handle = spawn_tokio!(async move {
let power_level = Int::new_saturating(power_level);
matrix_room
.update_power_levels(vec![(&user_id, power_level)])
.await
@ -587,16 +608,17 @@ impl Permissions {
return Err(());
};
let event = RoomPowerLevelsEventContent::try_from(power_levels).map_err(|error| {
error!("Could not set power levels: {error}");
})?;
let matrix_room = room.matrix_room().clone();
let handle = spawn_tokio!(async move {
let event = RoomPowerLevelsEventContent::from(power_levels);
matrix_room.send_state_event(event).await
});
let handle = spawn_tokio!(async move { matrix_room.send_state_event(event).await });
match handle.await.expect("task was not aborted") {
Ok(_) => Ok(()),
Err(error) => {
error!("Failed to set power levels: {error}");
error!("Could not set power levels: {error}");
Err(())
}
}
@ -624,6 +646,21 @@ impl Permissions {
}),
)
}
/// Connect to the signal emitted when the power level of our own member
/// changed.
pub(crate) fn connect_own_power_level_changed<F: Fn(&Self) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_closure(
"own-power-level-changed",
true,
closure_local!(move |obj: Self| {
f(&obj);
}),
)
}
}
impl Default for Permissions {

2
src/session/view/content/room_details/members_page/members_list_view/mod.rs

@ -216,7 +216,7 @@ mod imp {
.insert(kind, items_changed_handler);
// Sort the members list by power level, then display name.
let power_level_expr = Member::this_expression("power-level");
let power_level_expr = Member::this_expression("power-level-i64");
let sorter = gtk::MultiSorter::new();
sorter.append(
gtk::NumericSorter::builder()

13
src/session/view/content/room_details/permissions/add_members_subpage.rs

@ -3,7 +3,7 @@ use gtk::{
CompositeTemplate, glib,
glib::{clone, closure, closure_local},
};
use ruma::OwnedUserId;
use ruma::{Int, OwnedUserId, events::room::power_levels::UserPowerLevel};
use tracing::error;
use super::{MemberPowerLevel, PermissionsSelectMemberRow, PrivilegedMembers};
@ -125,7 +125,12 @@ mod imp {
// Since this is a view to add custom power levels, filter out members with
// a custom power level already.
member.power_level() == permissions.default_power_level()
if let UserPowerLevel::Int(power_level) = member.power_level() {
i64::from(power_level) == permissions.default_power_level()
} else {
// There should not be members with infinite power level.
false
}
}
));
}
@ -245,6 +250,8 @@ mod imp {
return;
}
let power_level = Int::new_saturating(power_level);
// Warn if power level is set at same level as own power level.
let is_own_power_level = power_level == permissions.own_power_level();
if is_own_power_level
@ -259,7 +266,7 @@ mod imp {
.into_iter()
.map(|(user_id, member)| {
let member = MemberPowerLevel::new(&member, &permissions);
member.set_power_level(power_level);
member.set_power_level(power_level.into());
(user_id, member)
});

114
src/session/view/content/room_details/permissions/member_power_level.rs

@ -1,19 +1,34 @@
use adw::subclass::prelude::*;
use gtk::{glib, glib::clone, prelude::*};
use ruma::{Int, OwnedUserId, events::room::power_levels::PowerLevelUserAction};
use gtk::{
glib,
glib::{clone, closure_local},
prelude::*,
};
use ruma::{
Int, OwnedUserId,
events::room::power_levels::{PowerLevelUserAction, UserPowerLevel},
int,
};
use tracing::error;
use crate::{
prelude::*,
session::model::{MemberRole, POWER_LEVEL_MAX, POWER_LEVEL_MIN, Permissions, PowerLevel, User},
session::model::{MemberRole, Permissions, User},
utils::BoundObjectWeakRef,
};
mod imp {
use std::cell::{Cell, OnceCell};
use std::{
cell::{Cell, OnceCell},
marker::PhantomData,
sync::LazyLock,
};
use glib::subclass::Signal;
use super::*;
#[derive(Debug, Default, glib::Properties)]
#[derive(Debug, glib::Properties)]
#[properties(wrapper_type = super::MemberPowerLevel)]
pub struct MemberPowerLevel {
/// The permissions to watch.
@ -26,8 +41,15 @@ mod imp {
///
/// Initially, it should be the same as the member's, but can change
/// independently.
#[property(get, set = Self::set_power_level, explicit_notify, minimum = POWER_LEVEL_MIN, maximum = POWER_LEVEL_MAX)]
power_level: Cell<PowerLevel>,
pub(super) power_level: Cell<UserPowerLevel>,
/// The wanted power level of the member, as an `i64`.
///
/// Should only be used for sorting.
///
/// `i64::MAX` is used to represent an infinite power level, since it
/// cannot be reached with the Matrix specification.
#[property(get = Self::power_level_i64)]
power_level_i64: PhantomData<i64>,
/// The wanted role of the member.
#[property(get, builder(MemberRole::default()))]
role: Cell<MemberRole>,
@ -36,6 +58,19 @@ mod imp {
editable: Cell<bool>,
}
impl Default for MemberPowerLevel {
fn default() -> Self {
Self {
permissions: Default::default(),
user: Default::default(),
power_level: Cell::new(UserPowerLevel::Int(int!(0))),
power_level_i64: Default::default(),
role: Default::default(),
editable: Default::default(),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for MemberPowerLevel {
const NAME: &'static str = "RoomDetailsPermissionsMemberPowerLevel";
@ -44,6 +79,12 @@ mod imp {
#[glib::derived_properties]
impl ObjectImpl for MemberPowerLevel {
fn signals() -> &'static [Signal] {
static SIGNALS: LazyLock<Vec<Signal>> =
LazyLock::new(|| vec![Signal::builder("power-level-changed").build()]);
SIGNALS.as_ref()
}
fn constructed(&self) {
self.parent_constructed();
@ -81,14 +122,28 @@ mod imp {
}
/// Set the wanted power level of the member.
fn set_power_level(&self, power_level: PowerLevel) {
pub(super) fn set_power_level(&self, power_level: UserPowerLevel) {
if self.power_level.get() == power_level {
return;
}
self.power_level.set(power_level);
self.update_role();
self.obj().notify_power_level();
let obj = self.obj();
obj.emit_by_name::<()>("power-level-changed", &[]);
obj.notify_power_level_i64();
}
/// The wanted power level of the member, as an `i64`.
fn power_level_i64(&self) -> i64 {
if let UserPowerLevel::Int(power_level) = self.power_level.get() {
power_level.into()
} else {
// Represent the infinite power level with a value out of range for a power
// level.
i64::MAX
}
}
/// Update the wanted role of the member.
@ -143,6 +198,16 @@ impl MemberPowerLevel {
.build()
}
/// The wanted power level of the member.
pub(crate) fn power_level(&self) -> UserPowerLevel {
self.imp().power_level.get()
}
/// Set the wanted power level of the member.
pub(crate) fn set_power_level(&self, power_level: UserPowerLevel) {
self.imp().set_power_level(power_level);
}
/// Get the parts of this member, to use in the power levels event.
///
/// Returns `None` if the permissions could not be upgraded, or if the power
@ -150,25 +215,38 @@ impl MemberPowerLevel {
pub(crate) fn to_parts(&self) -> Option<(OwnedUserId, Int)> {
let permissions = self.permissions()?;
let UserPowerLevel::Int(power_level) = self.power_level() else {
error!("Cannot set user power level to infinite");
return None;
};
let users_default = permissions.default_power_level();
let pl = self.power_level();
if pl == users_default {
if i64::from(power_level) == users_default {
return None;
}
Some((self.user().user_id().clone(), Int::new_saturating(pl)))
Some((self.user().user_id().clone(), power_level))
}
/// The string to use to search for this member.
pub(crate) fn search_string(&self) -> String {
let user = self.user();
format!(
"{} {} {} {}",
user.display_name(),
user.user_id(),
self.role(),
self.power_level(),
format!("{} {} {}", user.display_name(), user.user_id(), self.role())
}
/// Connect to the signal emitted when the power level of the member
/// changed.
pub(crate) fn connect_power_level_changed<F: Fn(&Self) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
self.connect_closure(
"power-level-changed",
true,
closure_local!(move |obj: Self| {
f(&obj);
}),
)
}
}

39
src/session/view/content/room_details/permissions/member_row.rs

@ -1,6 +1,7 @@
use std::slice;
use gtk::{CompositeTemplate, glib, glib::clone, prelude::*, subclass::prelude::*};
use ruma::{Int, events::room::power_levels::UserPowerLevel};
use super::MemberPowerLevel;
use crate::{
@ -103,7 +104,7 @@ mod imp {
self.member.disconnect_signals();
if let Some(member) = member {
let power_level_handler = member.connect_power_level_notify(clone!(
let power_level_handler = member.connect_power_level_changed(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
@ -127,14 +128,21 @@ mod imp {
self.obj().notify_member();
}
/// Update the power level label.
/// Update the power level.
fn update_power_level(&self) {
let Some(member) = self.member.obj() else {
return;
};
// We should only show power levels with a value.
let UserPowerLevel::Int(power_level) = member.power_level() else {
return;
};
self.selected_level_label
.set_label(&member.power_level().to_string());
.set_label(&power_level.to_string());
self.popover
.set_selected_power_level(i64::from(power_level));
}
/// Update the accessible role of this row.
@ -187,8 +195,12 @@ mod imp {
return;
};
let power_level = self.popover.selected_power_level();
let old_power_level = member.power_level();
let power_level = Int::new_saturating(self.popover.selected_power_level());
let UserPowerLevel::Int(old_power_level) = member.power_level() else {
// We should only edit power levels with a value.
return;
};
if power_level == old_power_level {
// Nothing changed.
@ -204,7 +216,7 @@ mod imp {
if room_power_level == power_level {
// The power level was reset to the one in the room, nothing to check.
member.set_power_level(power_level);
member.set_power_level(power_level.into());
return;
}
@ -214,18 +226,20 @@ mod imp {
// Warn that demoting oneself is irreversible.
if !confirm_own_demotion_dialog(&*obj).await {
// Reset the value in the popover.
self.popover.set_selected_power_level(old_power_level);
self.popover
.set_selected_power_level(i64::from(old_power_level));
return;
}
} else {
// Warn if user is muted but was not before.
let mute_power_level = permissions.mute_power_level();
let is_muted =
power_level <= mute_power_level && old_power_level > mute_power_level;
let is_muted = i64::from(power_level) <= mute_power_level
&& i64::from(old_power_level) > mute_power_level;
if is_muted && !confirm_mute_room_member_dialog(slice::from_ref(&user), &*obj).await
{
// Reset the value in the popover.
self.popover.set_selected_power_level(old_power_level);
self.popover
.set_selected_power_level(i64::from(old_power_level));
return;
}
@ -239,12 +253,13 @@ mod imp {
.await
{
// Reset the value in the popover.
self.popover.set_selected_power_level(old_power_level);
self.popover
.set_selected_power_level(i64::from(old_power_level));
return;
}
}
member.set_power_level(power_level);
member.set_power_level(power_level.into());
}
}
}

5
src/session/view/content/room_details/permissions/member_row.ui

@ -92,11 +92,6 @@
<object class="PowerLevelSelectionPopover" id="popover">
<signal name="notify::visible" handler="popover_visible" swapped="true" />
<signal name="notify::selected-power-level" handler="power_level_changed" swapped="true" />
<binding name="selected-power-level">
<lookup name="power-level">
<lookup name="member">RoomDetailsPermissionsMemberRow</lookup>
</lookup>
</binding>
</object>
</child>
</object>

2
src/session/view/content/room_details/permissions/members_subpage.rs

@ -104,7 +104,7 @@ mod imp {
self.update_visible_page();
// Sort members by power level, then display name, then user ID.
let power_level_expr = MemberPowerLevel::this_expression("power-level");
let power_level_expr = MemberPowerLevel::this_expression("power-level-i64");
let power_level_sorter = gtk::NumericSorter::builder()
.expression(power_level_expr)
.sort_order(gtk::SortType::Descending)

111
src/session/view/content/room_details/permissions/permissions_subpage.rs

@ -5,9 +5,10 @@ use ruma::{
Int,
events::{
StateEventType, TimelineEventType,
room::power_levels::{PowerLevelAction, RoomPowerLevels},
room::power_levels::{PowerLevelAction, RoomPowerLevels, UserPowerLevel},
},
};
use tracing::error;
use super::{PermissionsAddMembersSubpage, PermissionsMembersSubpage, PrivilegedMembers};
use crate::{
@ -15,7 +16,7 @@ use crate::{
ButtonCountRow, LoadingButton, PowerLevelSelectionRow, UnsavedChangesResponse,
unsaved_changes_dialog,
},
session::model::{Permissions, PowerLevel},
session::model::Permissions,
toast,
utils::BoundObjectWeakRef,
};
@ -26,6 +27,7 @@ mod imp {
use glib::subclass::InitializingObject;
use super::*;
use crate::session::model::POWER_LEVEL_MAX;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(
@ -235,7 +237,7 @@ mod imp {
};
let power_levels = permissions.power_levels();
let events_default = PowerLevel::from(power_levels.events_default);
let events_default = UserPowerLevel::from(power_levels.events_default);
if self.messages_row.selected_power_level() != events_default {
return true;
}
@ -254,12 +256,12 @@ mod imp {
return true;
}
let notify_room = PowerLevel::from(power_levels.notifications.room);
let notify_room = power_levels.notifications.room;
if self.notify_room_row.selected_power_level() != notify_room {
return true;
}
let state_default = PowerLevel::from(power_levels.state_default);
let state_default = UserPowerLevel::from(power_levels.state_default);
if self.state_row.selected_power_level() != state_default {
return true;
}
@ -336,23 +338,23 @@ mod imp {
return true;
}
let invite = PowerLevel::from(power_levels.invite);
let invite = power_levels.invite;
if self.invite_row.selected_power_level() != invite {
return true;
}
let kick = PowerLevel::from(power_levels.kick);
let kick = power_levels.kick;
if self.kick_row.selected_power_level() != kick {
return true;
}
let ban = PowerLevel::from(power_levels.ban);
let ban = power_levels.ban;
if self.ban_row.selected_power_level() != ban {
return true;
}
let default_pl = PowerLevel::from(power_levels.users_default);
self.members_default_adjustment.value() as PowerLevel != default_pl
let default_pl = i64::from(power_levels.users_default);
self.members_default_adjustment.value() as i64 != default_pl
}
/// Update the room actions section.
@ -365,15 +367,16 @@ mod imp {
let power_levels = permissions.power_levels();
let own_pl = permissions.own_power_level();
let events_default = PowerLevel::from(power_levels.events_default);
self.messages_row.set_selected_power_level(events_default);
let events_default = power_levels.events_default;
self.messages_row
.set_selected_power_level(events_default.into());
self.messages_row
.set_read_only(!editable || own_pl < events_default);
let redact_own = event_power_level(
&power_levels,
&TimelineEventType::RoomRedaction,
events_default,
events_default.into(),
);
self.redact_own_row.set_selected_power_level(redact_own);
self.redact_own_row
@ -385,12 +388,12 @@ mod imp {
self.redact_others_row
.set_read_only(!editable || own_pl < redact_others);
let notify_room = PowerLevel::from(power_levels.notifications.room);
let notify_room = power_levels.notifications.room.into();
self.notify_room_row.set_selected_power_level(notify_room);
self.notify_room_row
.set_read_only(!editable || own_pl < notify_room);
let state_default = PowerLevel::from(power_levels.state_default);
let state_default = power_levels.state_default.into();
self.state_row.set_selected_power_level(state_default);
self.state_row
.set_read_only(!editable || own_pl < state_default);
@ -490,15 +493,15 @@ mod imp {
let power_levels = permissions.power_levels();
let own_pl = permissions.own_power_level();
let invite = PowerLevel::from(power_levels.invite);
let invite = power_levels.invite.into();
self.invite_row.set_selected_power_level(invite);
self.invite_row.set_read_only(!editable || own_pl < invite);
let kick = PowerLevel::from(power_levels.kick);
let kick = power_levels.kick.into();
self.kick_row.set_selected_power_level(kick);
self.kick_row.set_read_only(!editable || own_pl < kick);
let ban = PowerLevel::from(power_levels.ban);
let ban = power_levels.ban.into();
self.ban_row.set_selected_power_level(ban);
self.ban_row.set_read_only(!editable || own_pl < ban);
}
@ -510,14 +513,21 @@ mod imp {
};
let power_levels = permissions.power_levels();
let default_pl = PowerLevel::from(power_levels.users_default);
self.members_default_adjustment.set_value(default_pl as f64);
let default_pl = power_levels.users_default;
self.members_default_adjustment
.set_value(i64::from(default_pl) as f64);
self.members_default_label
.set_label(&default_pl.to_string());
// We cannot change any required power level to something higher than ours.
let own_pl = permissions.own_power_level();
let max = default_pl.max(own_pl);
let own_max = if let UserPowerLevel::Int(pl) = own_pl {
// We cannot change any power level to something higher than ours.
i64::from(pl)
} else {
// We can change the power level to any valid value.
POWER_LEVEL_MAX
};
let max = i64::from(default_pl).max(own_max);
self.members_default_adjustment.set_upper(max as f64);
let editable = self.editable.get();
@ -579,13 +589,27 @@ mod imp {
/// Collect the current power levels.
///
/// Returns `None` if the permissions could not be upgraded.
#[allow(clippy::too_many_lines)]
fn collect_power_levels(&self) -> Option<RoomPowerLevels> {
macro_rules! set_power_level {
($power_levels:ident, $field:ident, $value:ident) => {
set_power_level_inner(&mut $power_levels.$field, stringify!($field), $value);
};
($power_levels:ident, $field:ident.$nested:ident , $value:ident) => {
set_power_level_inner(
&mut $power_levels.$field.$nested,
stringify!($field.$nested),
$value,
);
};
}
let permissions = self.permissions.obj()?;
let mut power_levels = permissions.power_levels();
let events_default = self.messages_row.selected_power_level();
power_levels.events_default = Int::new_saturating(events_default);
set_power_level!(power_levels, events_default, events_default);
let mut redact_own = self.redact_own_row.selected_power_level();
let redact_others = self.redact_others_row.selected_power_level();
@ -600,13 +624,13 @@ mod imp {
events_default,
);
power_levels.redact = Int::new_saturating(redact_others);
set_power_level!(power_levels, redact, redact_others);
let notify_room = self.notify_room_row.selected_power_level();
power_levels.notifications.room = Int::new_saturating(notify_room);
set_power_level!(power_levels, notifications.room, notify_room);
let state_default = self.state_row.selected_power_level();
power_levels.state_default = Int::new_saturating(state_default);
set_power_level!(power_levels, state_default, state_default);
let name = self.name_row.selected_power_level();
set_event_power_level(
@ -681,15 +705,15 @@ mod imp {
);
let invite = self.invite_row.selected_power_level();
power_levels.invite = Int::new_saturating(invite);
set_power_level!(power_levels, invite, invite);
let kick = self.kick_row.selected_power_level();
power_levels.kick = Int::new_saturating(kick);
set_power_level!(power_levels, kick, kick);
let ban = self.ban_row.selected_power_level();
power_levels.ban = Int::new_saturating(ban);
set_power_level!(power_levels, ban, ban);
let default_pl = self.members_default_adjustment.value() as PowerLevel;
let default_pl = self.members_default_adjustment.value() as i64;
power_levels.users_default = Int::new_saturating(default_pl);
let privileged_members = self.privileged_members();
@ -789,19 +813,32 @@ impl PermissionsSubpage {
}
}
/// Set the power level for the given field.
fn set_power_level_inner(current_value: &mut Int, field_name: &str, new_value: UserPowerLevel) {
let UserPowerLevel::Int(new_value) = new_value else {
error!("Cannot set power level for field `{field_name}` to infinite");
return;
};
*current_value = new_value;
}
/// Set the power level for the given event type in the given power levels.
fn set_event_power_level(
power_levels: &mut RoomPowerLevels,
event_type: TimelineEventType,
value: PowerLevel,
default: PowerLevel,
value: UserPowerLevel,
default: UserPowerLevel,
) {
if value == default {
power_levels.events.remove(&event_type);
} else {
power_levels
.events
.insert(event_type, Int::new_saturating(value));
let UserPowerLevel::Int(value) = value else {
error!("Cannot set power level for event `{event_type}` to infinite");
return;
};
power_levels.events.insert(event_type, value);
}
}
@ -810,8 +847,8 @@ fn set_event_power_level(
fn event_power_level(
power_levels: &RoomPowerLevels,
event_type: &TimelineEventType,
default: i64,
) -> i64 {
default: UserPowerLevel,
) -> UserPowerLevel {
power_levels
.events
.get(event_type)

34
src/session/view/content/room_details/permissions/permissions_subpage.ui

@ -48,7 +48,7 @@
<property name="title" translatable="yes">Send Messages</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -59,7 +59,7 @@
<property name="title" translatable="yes">Remove Own Messages</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="redact_own_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="redact_own_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -70,7 +70,7 @@
<property name="title" translatable="yes">Remove Messages of Other Members</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="redact_others_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="redact_others_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -81,7 +81,7 @@
<property name="title" translatable="yes">Notify Entire Room</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -92,7 +92,7 @@
<property name="title" translatable="yes">Change Room Settings</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="state_default_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="state_default_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -103,7 +103,7 @@
<property name="title" translatable="yes">Change Room Name</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -114,7 +114,7 @@
<property name="title" translatable="yes">Change Room Description</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -125,7 +125,7 @@
<property name="title" translatable="yes">Change Room Avatar</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -136,7 +136,7 @@
<property name="title" translatable="yes">Change Addresses</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -147,7 +147,7 @@
<property name="title" translatable="yes">Change Who Can Read History</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -158,7 +158,7 @@
<property name="title" translatable="yes">Enable Encryption</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -169,7 +169,7 @@
<property name="title" translatable="yes">Change Permissions</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -180,7 +180,7 @@
<property name="title" translatable="yes">Change Server Access Control List</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -191,7 +191,7 @@
<property name="title" translatable="yes">Upgrade Room</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -209,7 +209,7 @@
<property name="title" translatable="yes">Invite</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -221,7 +221,7 @@
<property name="title" translatable="yes">Kick</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>
@ -233,7 +233,7 @@
<property name="title" translatable="yes">Ban</property>
<property name="use-subtitle">True</property>
<property name="permissions" bind-source="RoomDetailsPermissionsSubpage" bind-property="permissions" bind-flags="sync-create"/>
<signal name="notify::selected-power-level" handler="value_changed" swapped="yes"/>
<signal name="selected-power-level-changed" handler="value_changed" swapped="yes"/>
<style>
<class name="property"/>
</style>

6
src/session/view/content/room_details/permissions/privileged_members.rs

@ -7,7 +7,7 @@ use ruma::{Int, OwnedUserId};
use super::MemberPowerLevel;
use crate::{
session::model::{Permissions, PowerLevel, User},
session::model::{Permissions, User},
utils::BoundObjectWeakRef,
};
@ -115,7 +115,7 @@ mod imp {
});
let member = MemberPowerLevel::new(&user, &permissions);
let handler = member.connect_power_level_notify(clone!(
let handler = member.connect_power_level_changed(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
@ -184,7 +184,7 @@ mod imp {
return true;
};
if member.power_level() != PowerLevel::from(*pl) {
if member.power_level() != *pl {
return true;
}
}

24
src/session/view/content/room_history/message_toolbar/mod.rs

@ -8,13 +8,12 @@ use gtk::{
glib::{self, clone},
};
use matrix_sdk::{
attachment::{AttachmentConfig, AttachmentInfo, BaseFileInfo, Thumbnail},
room::{
edit::EditedContent,
reply::{EnforceThread, Reply},
},
attachment::{AttachmentInfo, BaseFileInfo, Thumbnail},
room::edit::EditedContent,
};
use matrix_sdk_ui::timeline::{
AttachmentConfig, AttachmentSource, TimelineEventItemId, TimelineItemContent,
};
use matrix_sdk_ui::timeline::{AttachmentSource, TimelineEventItemId, TimelineItemContent};
use ruma::{
OwnedRoomId,
events::{
@ -693,14 +692,11 @@ mod imp {
// Send event depending on relation.
match composer_state.related_to() {
Some(RelationInfo::Reply(message_event)) => {
let reply = Reply {
event_id: message_event.event_id(),
enforce_thread: EnforceThread::MaybeThreaded,
};
let event_id = message_event.event_id();
let handle =
spawn_tokio!(
async move { matrix_timeline.send_reply(content, reply).await }
async move { matrix_timeline.send_reply(content, event_id).await }
);
if let Err(error) = handle.await.expect("task was not aborted") {
@ -888,7 +884,11 @@ mod imp {
return;
};
let config = AttachmentConfig::new().thumbnail(thumbnail).info(info);
let config = AttachmentConfig {
info: Some(info),
thumbnail,
..Default::default()
};
let matrix_timeline = timeline.matrix_timeline();

18
src/session/view/content/room_history/sender_avatar/mod.rs

@ -3,7 +3,10 @@ use std::slice;
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::{gettext, ngettext};
use gtk::{CompositeTemplate, gdk, glib, glib::clone};
use ruma::{OwnedEventId, events::room::power_levels::PowerLevelUserAction};
use ruma::{
Int, OwnedEventId,
events::room::power_levels::{PowerLevelUserAction, UserPowerLevel},
};
use crate::{
Window,
@ -247,7 +250,7 @@ mod imp {
}
));
let power_level_handler = sender.connect_power_level_notify(clone!(
let power_level_handler = sender.connect_power_level_changed(clone!(
#[weak(rename_to = imp)]
self,
move |_| {
@ -610,9 +613,14 @@ mod imp {
let Some(sender) = self.sender.obj() else {
return;
};
let obj = self.obj();
let old_power_level = sender.power_level();
let UserPowerLevel::Int(old_power_level) = sender.power_level() else {
// We cannot mute someone with an infinite power level.
return;
};
let old_power_level = i64::from(old_power_level);
let obj = self.obj();
let permissions = sender.room().permissions();
// Warn if user is muted but was not before.
@ -635,7 +643,7 @@ mod imp {
toast!(obj, text);
let text = if permissions
.set_user_power_level(user_id, new_power_level)
.set_user_power_level(user_id, Int::new_saturating(new_power_level))
.await
.is_ok()
{

Loading…
Cancel
Save