13 changed files with 459 additions and 70 deletions
|
After Width: | Height: | Size: 813 B |
@ -0,0 +1,284 @@
|
||||
use adw::{prelude::*, subclass::prelude::*}; |
||||
use gettextrs::gettext; |
||||
use gtk::{CompositeTemplate, glib, glib::clone}; |
||||
use ruma::events::room::join_rules::JoinRule as MatrixJoinRule; |
||||
|
||||
use crate::{ |
||||
components::{CheckLoadingRow, LoadingButton, UnsavedChangesResponse, unsaved_changes_dialog}, |
||||
session::model::{JoinRuleValue, Room}, |
||||
toast, |
||||
}; |
||||
|
||||
mod imp { |
||||
use std::cell::{Cell, RefCell}; |
||||
|
||||
use glib::subclass::InitializingObject; |
||||
use ruma::events::{StateEventType, room::power_levels::PowerLevelAction}; |
||||
|
||||
use super::*; |
||||
|
||||
#[derive(Debug, Default, CompositeTemplate, glib::Properties)] |
||||
#[template(
|
||||
resource = "/org/gnome/Fractal/ui/session/view/content/room_details/join_rule_subpage.ui" |
||||
)] |
||||
#[properties(wrapper_type = super::JoinRuleSubpage)] |
||||
pub struct JoinRuleSubpage { |
||||
#[template_child] |
||||
save_button: TemplateChild<LoadingButton>, |
||||
#[template_child] |
||||
info_box: TemplateChild<gtk::Box>, |
||||
#[template_child] |
||||
info_image: TemplateChild<gtk::Image>, |
||||
#[template_child] |
||||
info_description: TemplateChild<gtk::Label>, |
||||
#[template_child] |
||||
knock_box: TemplateChild<gtk::ListBox>, |
||||
#[template_child] |
||||
knock_row: TemplateChild<adw::SwitchRow>, |
||||
/// The presented room.
|
||||
#[property(get, set = Self::set_room, explicit_notify, nullable)] |
||||
room: glib::WeakRef<Room>, |
||||
/// The local value of the join rule.
|
||||
#[property(get, set = Self::set_local_value, explicit_notify, builder(JoinRuleValue::default()))] |
||||
local_value: Cell<JoinRuleValue>, |
||||
/// Whether the join rule was changed by the user.
|
||||
#[property(get)] |
||||
changed: Cell<bool>, |
||||
permissions_handler: RefCell<Option<glib::SignalHandlerId>>, |
||||
join_rule_handler: RefCell<Option<glib::SignalHandlerId>>, |
||||
} |
||||
|
||||
#[glib::object_subclass] |
||||
impl ObjectSubclass for JoinRuleSubpage { |
||||
const NAME: &'static str = "RoomDetailsJoinRuleSubpage"; |
||||
type Type = super::JoinRuleSubpage; |
||||
type ParentType = adw::NavigationPage; |
||||
|
||||
fn class_init(klass: &mut Self::Class) { |
||||
CheckLoadingRow::ensure_type(); |
||||
|
||||
Self::bind_template(klass); |
||||
Self::bind_template_callbacks(klass); |
||||
|
||||
klass.install_property_action("join-rule.set-value", "local-value"); |
||||
} |
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) { |
||||
obj.init_template(); |
||||
} |
||||
} |
||||
|
||||
#[glib::derived_properties] |
||||
impl ObjectImpl for JoinRuleSubpage { |
||||
fn dispose(&self) { |
||||
self.disconnect_signals(); |
||||
} |
||||
} |
||||
|
||||
impl WidgetImpl for JoinRuleSubpage {} |
||||
impl NavigationPageImpl for JoinRuleSubpage {} |
||||
|
||||
#[gtk::template_callbacks] |
||||
impl JoinRuleSubpage { |
||||
/// Set the presented room.
|
||||
fn set_room(&self, room: Option<&Room>) { |
||||
let Some(room) = room else { |
||||
// Just ignore when room is missing.
|
||||
return; |
||||
}; |
||||
|
||||
self.disconnect_signals(); |
||||
|
||||
let permissions_handler = room.permissions().connect_changed(clone!( |
||||
#[weak(rename_to = imp)] |
||||
self, |
||||
move |_| { |
||||
imp.update(); |
||||
} |
||||
)); |
||||
self.permissions_handler.replace(Some(permissions_handler)); |
||||
|
||||
let join_rule_handler = room.join_rule().connect_changed(clone!( |
||||
#[weak(rename_to = imp)] |
||||
self, |
||||
move |_| { |
||||
imp.update(); |
||||
} |
||||
)); |
||||
self.join_rule_handler.replace(Some(join_rule_handler)); |
||||
|
||||
let supports_knocking = room.rules().authorization.knocking; |
||||
if !supports_knocking { |
||||
self.info_description.set_label(&gettext("The version of this room does not support all possibilities. Upgrade this room to the latest version to see more options.")); |
||||
self.info_image.set_icon_name(Some("info-symbolic")); |
||||
} |
||||
|
||||
self.info_box.set_visible(!supports_knocking); |
||||
self.knock_box.set_visible(supports_knocking); |
||||
|
||||
self.room.set(Some(room)); |
||||
|
||||
self.update(); |
||||
self.obj().notify_room(); |
||||
} |
||||
|
||||
/// Update the subpage.
|
||||
fn update(&self) { |
||||
let Some(room) = self.room.upgrade() else { |
||||
return; |
||||
}; |
||||
|
||||
let join_rule = room.join_rule(); |
||||
self.set_local_value(join_rule.value()); |
||||
self.knock_row.set_active(join_rule.can_knock()); |
||||
|
||||
self.save_button.set_is_loading(false); |
||||
self.update_changed(); |
||||
} |
||||
|
||||
/// Set the local value of the join rule.
|
||||
fn set_local_value(&self, value: JoinRuleValue) { |
||||
if self.local_value.get() == value { |
||||
return; |
||||
} |
||||
|
||||
self.local_value.set(value); |
||||
|
||||
let can_knock = matches!(value, JoinRuleValue::Invite | JoinRuleValue::RoomMembership); |
||||
self.knock_box.set_sensitive(can_knock); |
||||
|
||||
self.update_changed(); |
||||
self.obj().notify_local_value(); |
||||
} |
||||
|
||||
/// Whether we can change the join rule.
|
||||
fn can_change(&self) -> bool { |
||||
let Some(room) = self.room.upgrade() else { |
||||
return false; |
||||
}; |
||||
|
||||
if !room.join_rule().value().can_be_edited() { |
||||
return false; |
||||
} |
||||
|
||||
room.permissions() |
||||
.is_allowed_to(PowerLevelAction::SendState(StateEventType::RoomJoinRules)) |
||||
} |
||||
|
||||
/// Whether users can request invites.
|
||||
fn can_knock(&self) -> bool { |
||||
self.knock_box.is_visible() |
||||
&& self.knock_box.is_sensitive() |
||||
&& self.knock_row.is_active() |
||||
} |
||||
|
||||
/// Compute the new join rule from the current state.
|
||||
fn new_join_rule(&self) -> MatrixJoinRule { |
||||
match self.local_value.get() { |
||||
JoinRuleValue::Invite => { |
||||
if self.can_knock() { |
||||
MatrixJoinRule::Knock |
||||
} else { |
||||
MatrixJoinRule::Invite |
||||
} |
||||
} |
||||
JoinRuleValue::Public => MatrixJoinRule::Public, |
||||
_ => unimplemented!(), |
||||
} |
||||
} |
||||
|
||||
/// Update whether the join rule was changed by the user.
|
||||
#[template_callback] |
||||
fn update_changed(&self) { |
||||
let Some(room) = self.room.upgrade() else { |
||||
return; |
||||
}; |
||||
|
||||
let changed = if self.can_change() { |
||||
let current_join_rule = room |
||||
.join_rule() |
||||
.matrix_join_rule() |
||||
.unwrap_or(MatrixJoinRule::Invite); |
||||
let new_join_rule = self.new_join_rule(); |
||||
|
||||
current_join_rule != new_join_rule |
||||
} else { |
||||
false |
||||
}; |
||||
|
||||
self.changed.set(changed); |
||||
self.obj().notify_changed(); |
||||
} |
||||
|
||||
/// Save the changes of this page.
|
||||
#[template_callback] |
||||
async fn save(&self) { |
||||
if !self.changed.get() { |
||||
// Nothing to do.
|
||||
return; |
||||
} |
||||
|
||||
let Some(room) = self.room.upgrade() else { |
||||
return; |
||||
}; |
||||
|
||||
self.save_button.set_is_loading(true); |
||||
|
||||
let rule = self.new_join_rule(); |
||||
|
||||
if room.join_rule().set_matrix_join_rule(rule).await.is_err() { |
||||
toast!(self.obj(), gettext("Could not change who can join")); |
||||
self.save_button.set_is_loading(false); |
||||
} |
||||
} |
||||
|
||||
/// Go back to the previous page in the room details.
|
||||
///
|
||||
/// If there are changes in the page, ask the user to confirm.
|
||||
#[template_callback] |
||||
async fn go_back(&self) { |
||||
let obj = self.obj(); |
||||
let mut reset_after = false; |
||||
|
||||
if self.changed.get() { |
||||
match unsaved_changes_dialog(&*obj).await { |
||||
UnsavedChangesResponse::Save => self.save().await, |
||||
UnsavedChangesResponse::Discard => reset_after = true, |
||||
UnsavedChangesResponse::Cancel => return, |
||||
} |
||||
} |
||||
|
||||
let _ = obj.activate_action("navigation.pop", None); |
||||
|
||||
if reset_after { |
||||
self.update(); |
||||
} |
||||
} |
||||
|
||||
/// Disconnect all the signal handlers.
|
||||
fn disconnect_signals(&self) { |
||||
if let Some(room) = self.room.upgrade() { |
||||
if let Some(handler) = self.permissions_handler.take() { |
||||
room.permissions().disconnect(handler); |
||||
} |
||||
|
||||
if let Some(handler) = self.join_rule_handler.take() { |
||||
room.join_rule().disconnect(handler); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
glib::wrapper! { |
||||
/// Subpage to select the join rule of a room.
|
||||
pub struct JoinRuleSubpage(ObjectSubclass<imp::JoinRuleSubpage>) |
||||
@extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible; |
||||
} |
||||
|
||||
impl JoinRuleSubpage { |
||||
/// Construct a new `JoinRuleSubpage` for the given room.
|
||||
pub fn new(room: &Room) -> Self { |
||||
glib::Object::builder().property("room", room).build() |
||||
} |
||||
} |
||||
@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<interface> |
||||
<template class="RoomDetailsJoinRuleSubpage" parent="AdwNavigationPage"> |
||||
<property name="title" translatable="yes">Who Can Join</property> |
||||
<style> |
||||
<class name="form-page"/> |
||||
</style> |
||||
<property name="child"> |
||||
<object class="AdwToolbarView"> |
||||
<child type="top"> |
||||
<object class="AdwHeaderBar"> |
||||
<property name="show-back-button">False</property> |
||||
<child type="start"> |
||||
<object class="GtkButton"> |
||||
<property name="icon-name">go-previous-symbolic</property> |
||||
<property name="tooltip-text" translatable="yes">Back</property> |
||||
<signal name="clicked" handler="go_back" swapped="yes"/> |
||||
<style> |
||||
<class name="back"/> |
||||
</style> |
||||
</object> |
||||
</child> |
||||
<child type="end"> |
||||
<object class="LoadingButton" id="save_button"> |
||||
<property name="sensitive" bind-source="RoomDetailsJoinRuleSubpage" bind-property="changed" bind-flags="sync-create" /> |
||||
<property name="content-label" translatable="yes">_Save</property> |
||||
<property name="use-underline">True</property> |
||||
<signal name="clicked" handler="save" swapped="yes"/> |
||||
<style> |
||||
<class name="suggested-action"/> |
||||
</style> |
||||
</object> |
||||
</child> |
||||
</object> |
||||
</child> |
||||
<property name="content"> |
||||
<object class="GtkScrolledWindow" id="scrolled_window"> |
||||
<property name="hscrollbar-policy">never</property> |
||||
<property name="propagate-natural-height">True</property> |
||||
<property name="child"> |
||||
<object class="AdwClamp"> |
||||
<property name="child"> |
||||
<object class="GtkBox"> |
||||
<property name="orientation">vertical</property> |
||||
<child> |
||||
<object class="GtkBox" id="info_box"> |
||||
<property name="visible">False</property> |
||||
<property name="margin-start">12</property> |
||||
<property name="margin-end">12</property> |
||||
<property name="spacing">12</property> |
||||
<style> |
||||
<class name="dimmed"/> |
||||
</style> |
||||
<child> |
||||
<object class="GtkImage" id="info_image"> |
||||
<property name="pixel-size">24</property> |
||||
<property name="accessible-role">presentation</property> |
||||
</object> |
||||
</child> |
||||
<child> |
||||
<object class="GtkLabel" id="info_description"> |
||||
<property name="wrap">True</property> |
||||
<property name="wrap-mode">word-char</property> |
||||
<property name="xalign">0.0</property> |
||||
<style> |
||||
<class name="body"/> |
||||
</style> |
||||
</object> |
||||
</child> |
||||
</object> |
||||
</child> |
||||
<child> |
||||
<object class="GtkListBox"> |
||||
<child> |
||||
<object class="CheckLoadingRow"> |
||||
<property name="title" translatable="yes">Only Invited Users</property> |
||||
<property name="action-name">join-rule.set-value</property> |
||||
<property name="action-target">'invite'</property> |
||||
</object> |
||||
</child> |
||||
<child> |
||||
<object class="CheckLoadingRow"> |
||||
<property name="title" translatable="yes">Any Registered User</property> |
||||
<property name="action-name">join-rule.set-value</property> |
||||
<property name="action-target">'public'</property> |
||||
</object> |
||||
</child> |
||||
<style> |
||||
<class name="boxed-list"/> |
||||
</style> |
||||
</object> |
||||
</child> |
||||
<child> |
||||
<object class="GtkListBox" id="knock_box"> |
||||
<child> |
||||
<object class="AdwSwitchRow" id="knock_row"> |
||||
<property name="selectable">False</property> |
||||
<property name="title" translatable="yes">Allow Invite Requests</property> |
||||
<signal name="notify::active" handler="update_changed" swapped="true"/> |
||||
</object> |
||||
</child> |
||||
<style> |
||||
<class name="boxed-list"/> |
||||
</style> |
||||
</object> |
||||
</child> |
||||
</object> |
||||
</property> |
||||
</object> |
||||
</property> |
||||
</object> |
||||
</property> |
||||
</object> |
||||
</property> |
||||
</template> |
||||
</interface> |
||||
Loading…
Reference in new issue