diff --git a/src/components/dialogs/message_dialogs.rs b/src/components/dialogs/message_dialogs.rs index 53285ba9..bea05bbf 100644 --- a/src/components/dialogs/message_dialogs.rs +++ b/src/components/dialogs/message_dialogs.rs @@ -7,7 +7,7 @@ use crate::{ i18n::gettext_f, ngettext_f, prelude::*, - session::model::{Member, Membership, Room, RoomCategory}, + session::model::{Member, Membership, Room, RoomCategory, User}, }; /// Show a dialog to confirm leaving a room. @@ -358,7 +358,7 @@ pub(crate) struct ConfirmRoomMemberDestructiveActionResponse { /// Show a dialog to confirm muting one or several room members. pub(crate) async fn confirm_mute_room_member_dialog( - members: &[Member], + members: &[impl IsA], parent: &impl IsA, ) -> bool { if members.is_empty() { @@ -367,7 +367,8 @@ pub(crate) async fn confirm_mute_room_member_dialog( let first_member = members .first() - .expect("there should be at least one member"); + .expect("there should be at least one member") + .upcast_ref(); let count = members.len() as u32; let heading = ngettext_f( @@ -406,7 +407,7 @@ pub(crate) async fn confirm_mute_room_member_dialog( /// Show a dialog to confirm setting the power level of one or several room /// members with the same value as our own. pub(crate) async fn confirm_set_room_member_power_level_same_as_own_dialog( - members: &[Member], + members: &[impl IsA], parent: &impl IsA, ) -> bool { if members.is_empty() { @@ -415,7 +416,8 @@ pub(crate) async fn confirm_set_room_member_power_level_same_as_own_dialog( let first_member = members .first() - .expect("there should be at least one member"); + .expect("there should be at least one member") + .upcast_ref(); let count = members.len() as u32; let heading = ngettext_f( diff --git a/src/session/model/room/permissions.rs b/src/session/model/room/permissions.rs index 2fc3c815..c9558d19 100644 --- a/src/session/model/room/permissions.rs +++ b/src/session/model/room/permissions.rs @@ -500,6 +500,11 @@ impl Permissions { self.imp().power_levels.borrow().clone() } + /// 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() + } + /// The current [`MemberRole`] for the given power level. pub(crate) fn role(&self, power_level: PowerLevel) -> MemberRole { if power_level >= POWER_LEVEL_ADMIN { diff --git a/src/session/view/content/room_details/permissions/member_power_level.rs b/src/session/view/content/room_details/permissions/member_power_level.rs index 91492a03..ba6ad56c 100644 --- a/src/session/view/content/room_details/permissions/member_power_level.rs +++ b/src/session/view/content/room_details/permissions/member_power_level.rs @@ -77,10 +77,7 @@ mod imp { return; }; - let power_levels = permissions.power_levels(); - let power_level = power_levels.for_user(user.user_id()); - - self.set_power_level(power_level.into()); + self.set_power_level(permissions.user_power_level(user.user_id())); } /// Set the wanted power level of the member. diff --git a/src/session/view/content/room_details/permissions/member_row.rs b/src/session/view/content/room_details/permissions/member_row.rs index 7fd7f376..1029d6d2 100644 --- a/src/session/view/content/room_details/permissions/member_row.rs +++ b/src/session/view/content/room_details/permissions/member_row.rs @@ -1,8 +1,14 @@ +use std::slice; + use gtk::{CompositeTemplate, glib, glib::clone, prelude::*, subclass::prelude::*}; use super::MemberPowerLevel; use crate::{ - components::{Avatar, PowerLevelSelectionPopover, RoleBadge}, + components::{ + Avatar, PowerLevelSelectionPopover, RoleBadge, confirm_mute_room_member_dialog, + confirm_own_demotion_dialog, confirm_set_room_member_power_level_same_as_own_dialog, + }, + prelude::*, session::model::Permissions, utils::{BoundObject, key_bindings}, }; @@ -82,7 +88,9 @@ mod imp { impl PermissionsMemberRow { /// Set the permissions of the room. fn set_permissions(&self, permissions: Permissions) { - self.permissions.set(permissions.clone()).unwrap(); + self.permissions + .set(permissions.clone()) + .expect("permissions should be uninitialized"); self.popover.set_permissions(Some(permissions)); } @@ -174,13 +182,69 @@ mod imp { /// The popover's selected power level changed. #[template_callback] - fn power_level_changed(&self) { + async fn power_level_changed(&self) { let Some(member) = self.member.obj() else { return; }; - let pl = self.popover.selected_power_level(); - member.set_power_level(pl); + let power_level = self.popover.selected_power_level(); + let old_power_level = member.power_level(); + + if power_level == old_power_level { + // Nothing changed. + return; + } + + let permissions = self + .permissions + .get() + .expect("permissions should be initialized"); + let user = member.user(); + let room_power_level = permissions.user_power_level(user.user_id()); + + 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); + return; + } + + let obj = self.obj(); + + if user.is_own_user() { + // 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); + 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; + 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); + return; + } + + // 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 + && !confirm_set_room_member_power_level_same_as_own_dialog( + slice::from_ref(&user), + &*obj, + ) + .await + { + // Reset the value in the popover. + self.popover.set_selected_power_level(old_power_level); + return; + } + } + + member.set_power_level(power_level); } } }