Browse Source

room-creation: Rename to CreateRoomDialog, refactor and clean up

af/unable-to-decryt-styling
Kévin Commaille 12 months ago
parent
commit
2c84f96d46
No known key found for this signature in database
GPG Key ID: C971D9DBC9D678D
  1. 4
      po/POTFILES.in
  2. 257
      src/session/view/create_room_dialog.rs
  3. 2
      src/session/view/create_room_dialog.ui
  4. 6
      src/session/view/mod.rs
  5. 263
      src/session/view/room_creation.rs
  6. 10
      src/session/view/session_view.rs
  7. 2
      src/session/view/sidebar/mod.ui
  8. 2
      src/ui-resources.gresource.xml

4
po/POTFILES.in

@ -178,12 +178,12 @@ src/session/view/content/room_history/typing_row.rs
src/session/view/content/room_history/verification_info_bar.rs
src/session/view/create_dm_dialog/mod.rs
src/session/view/create_dm_dialog/mod.ui
src/session/view/create_room_dialog.rs
src/session/view/create_room_dialog.ui
src/session/view/event_details_dialog.rs
src/session/view/event_details_dialog.ui
src/session/view/media_viewer.rs
src/session/view/media_viewer.ui
src/session/view/room_creation.rs
src/session/view/room_creation.ui
src/session/view/sidebar/mod.rs
src/session/view/sidebar/mod.ui
src/session/view/sidebar/room_row.rs

257
src/session/view/create_room_dialog.rs

@ -0,0 +1,257 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, CompositeTemplate};
use matrix_sdk::{
ruma::{
api::client::{
error::ErrorKind,
room::{create_room, Visibility},
},
assign,
},
Error,
};
use ruma::events::{room::encryption::RoomEncryptionEventContent, InitialStateEvent};
use tracing::error;
use crate::{
components::{LoadingButton, SubstringEntryRow, ToastableDialog},
prelude::*,
session::model::Session,
spawn_tokio, toast, Window,
};
// MAX length of room addresses
const MAX_BYTES: usize = 255;
mod imp {
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/create_room_dialog.ui")]
#[properties(wrapper_type = super::CreateRoomDialog)]
pub struct CreateRoomDialog {
#[template_child]
create_button: TemplateChild<LoadingButton>,
#[template_child]
content: TemplateChild<gtk::Box>,
#[template_child]
room_name: TemplateChild<adw::EntryRow>,
#[template_child]
topic_text_view: TemplateChild<gtk::TextView>,
#[template_child]
visibility_private: TemplateChild<gtk::CheckButton>,
#[template_child]
encryption: TemplateChild<adw::SwitchRow>,
#[template_child]
room_address: TemplateChild<SubstringEntryRow>,
#[template_child]
room_address_error_revealer: TemplateChild<gtk::Revealer>,
#[template_child]
room_address_error: TemplateChild<gtk::Label>,
/// The current session.
#[property(get, set = Self::set_session, explicit_notify, nullable)]
session: glib::WeakRef<Session>,
}
#[glib::object_subclass]
impl ObjectSubclass for CreateRoomDialog {
const NAME: &'static str = "CreateRoomDialog";
type Type = super::CreateRoomDialog;
type ParentType = ToastableDialog;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
Self::bind_template_callbacks(klass);
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for CreateRoomDialog {}
impl WidgetImpl for CreateRoomDialog {}
impl AdwDialogImpl for CreateRoomDialog {}
impl ToastableDialogImpl for CreateRoomDialog {}
#[gtk::template_callbacks]
impl CreateRoomDialog {
/// Set the current session.
fn set_session(&self, session: Option<&Session>) {
if self.session.upgrade().as_ref() == session {
return;
}
if let Some(session) = session {
let server_name = session.user_id().server_name();
self.room_address.set_suffix_text(format!(":{server_name}"));
}
self.session.set(session);
self.obj().notify_session();
}
/// Check whether a room can be created with the current input.
///
/// This will also change the UI elements to reflect why the room can't
/// be created.
fn can_create_room(&self) -> bool {
if self.room_name.text().trim().is_empty() {
return false;
}
// Only public rooms have an address.
if self.visibility_private.is_active() {
return true;
}
let mut can_create = true;
let room_address = self.room_address.text();
// We don't allow #, : in the room address
let address_error = if room_address.contains(':') {
can_create = false;
Some(gettext("Cannot contain “:”"))
} else if room_address.contains('#') {
can_create = false;
Some(gettext("Cannot contain “#”"))
} else if room_address.len() > MAX_BYTES {
can_create = false;
Some(gettext("Too long. Use a shorter address."))
} else if room_address.trim().is_empty() {
can_create = false;
None
} else {
None
};
let reveal_address_error = address_error.is_some();
if let Some(error) = address_error {
self.room_address_error.set_text(&error);
self.room_address.add_css_class("error");
} else {
self.room_address.remove_css_class("error");
}
self.room_address_error_revealer
.set_reveal_child(reveal_address_error);
can_create
}
/// Validate the form and change the corresponding UI elements.
#[template_callback]
fn validate_form(&self) {
self.create_button.set_sensitive(self.can_create_room());
}
/// Create the room, if it is allowed.
#[template_callback]
async fn create_room(&self) {
if !self.can_create_room() {
return;
}
let Some(session) = self.session.upgrade() else {
return;
};
self.create_button.set_is_loading(true);
self.content.set_sensitive(false);
let name = Some(self.room_name.text().trim())
.filter(|s| !s.is_empty())
.map(ToOwned::to_owned);
let buffer = self.topic_text_view.buffer();
let (start_iter, end_iter) = buffer.bounds();
let topic = Some(buffer.text(&start_iter, &end_iter, false).trim())
.filter(|s| !s.is_empty())
.map(ToOwned::to_owned);
let mut request = assign!(
create_room::v3::Request::new(),
{
name,
topic,
}
);
if self.visibility_private.is_active() {
// The room is private.
request.visibility = Visibility::Private;
if self.encryption.is_active() {
let event = InitialStateEvent::new(
RoomEncryptionEventContent::with_recommended_defaults(),
);
request.initial_state = vec![event.to_raw_any()];
}
} else {
// The room is public.
request.visibility = Visibility::Public;
request.room_alias_name = Some(self.room_address.text().trim().to_owned());
}
let client = session.client();
let handle = spawn_tokio!(async move { client.create_room(request).await });
match handle.await.expect("task was not aborted") {
Ok(matrix_room) => {
let obj = self.obj();
let Some(window) = obj.root().and_downcast::<Window>() else {
return;
};
if let Some(room) = session.room_list().get_wait(matrix_room.room_id()).await {
window.session_view().select_room(room);
}
obj.close();
}
Err(error) => {
error!("Could not create a new room: {error}");
self.handle_error(&error);
}
}
}
/// Display the error that occurred during creation.
fn handle_error(&self, error: &Error) {
self.create_button.set_is_loading(false);
self.content.set_sensitive(true);
// Handle the room address already taken error.
if let Some(kind) = error.client_api_error_kind() {
if *kind == ErrorKind::RoomInUse {
self.room_address.add_css_class("error");
self.room_address_error
.set_text(&gettext("The address is already taken."));
self.room_address_error_revealer.set_reveal_child(true);
return;
}
}
let obj = self.obj();
toast!(obj, error.to_user_facing());
}
}
}
glib::wrapper! {
/// Dialog to create a new room.
pub struct CreateRoomDialog(ObjectSubclass<imp::CreateRoomDialog>)
@extends gtk::Widget, adw::Dialog, ToastableDialog, @implements gtk::Accessible;
}
impl CreateRoomDialog {
pub fn new(session: &Session) -> Self {
glib::Object::builder().property("session", session).build()
}
}

2
src/session/view/room_creation.ui → src/session/view/create_room_dialog.ui

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="RoomCreation" parent="ToastableDialog">
<template class="CreateRoomDialog" parent="ToastableDialog">
<property name="default-widget">create_button</property>
<property name="content-width">380</property>
<property name="child-content">

6
src/session/view/mod.rs

@ -1,14 +1,14 @@
mod account_settings;
mod content;
mod create_dm_dialog;
mod create_room_dialog;
mod event_details_dialog;
mod media_viewer;
mod room_creation;
mod session_view;
mod sidebar;
pub use self::{account_settings::AccountSettings, session_view::SessionView};
use self::{
content::Content, create_dm_dialog::CreateDmDialog, event_details_dialog::EventDetailsDialog,
media_viewer::MediaViewer, room_creation::RoomCreation, sidebar::Sidebar,
content::Content, create_dm_dialog::CreateDmDialog, create_room_dialog::CreateRoomDialog,
event_details_dialog::EventDetailsDialog, media_viewer::MediaViewer, sidebar::Sidebar,
};

263
src/session/view/room_creation.rs

@ -1,263 +0,0 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, CompositeTemplate};
use matrix_sdk::{
ruma::{
api::client::{
error::ErrorKind,
room::{create_room, Visibility},
},
assign,
},
Error,
};
use ruma::events::{room::encryption::RoomEncryptionEventContent, InitialStateEvent};
use tracing::error;
use crate::{
components::{LoadingButton, SubstringEntryRow, ToastableDialog},
prelude::*,
session::model::Session,
spawn_tokio, toast, Window,
};
// MAX length of room addresses
const MAX_BYTES: usize = 255;
mod imp {
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/room_creation.ui")]
#[properties(wrapper_type = super::RoomCreation)]
pub struct RoomCreation {
/// The current session.
#[property(get, set = Self::set_session, explicit_notify, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub create_button: TemplateChild<LoadingButton>,
#[template_child]
pub content: TemplateChild<gtk::Box>,
#[template_child]
pub room_name: TemplateChild<adw::EntryRow>,
#[template_child]
pub topic_text_view: TemplateChild<gtk::TextView>,
#[template_child]
pub visibility_private: TemplateChild<gtk::CheckButton>,
#[template_child]
pub encryption: TemplateChild<adw::SwitchRow>,
#[template_child]
pub room_address: TemplateChild<SubstringEntryRow>,
#[template_child]
pub room_address_error_revealer: TemplateChild<gtk::Revealer>,
#[template_child]
pub room_address_error: TemplateChild<gtk::Label>,
}
#[glib::object_subclass]
impl ObjectSubclass for RoomCreation {
const NAME: &'static str = "RoomCreation";
type Type = super::RoomCreation;
type ParentType = ToastableDialog;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
Self::Type::bind_template_callbacks(klass);
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for RoomCreation {}
impl WidgetImpl for RoomCreation {}
impl AdwDialogImpl for RoomCreation {}
impl ToastableDialogImpl for RoomCreation {}
impl RoomCreation {
/// Set the current session.
fn set_session(&self, session: Option<&Session>) {
if self.session.upgrade().as_ref() == session {
return;
}
if let Some(session) = session {
let server_name = session.user_id().server_name();
self.room_address.set_suffix_text(format!(":{server_name}"));
}
self.session.set(session);
self.obj().notify_session();
}
}
}
glib::wrapper! {
/// Dialog to create a new room.
pub struct RoomCreation(ObjectSubclass<imp::RoomCreation>)
@extends gtk::Widget, adw::Dialog, ToastableDialog, @implements gtk::Accessible;
}
#[gtk::template_callbacks]
impl RoomCreation {
pub fn new(session: &Session) -> Self {
glib::Object::builder().property("session", session).build()
}
/// Create the room, if it is allowed.
#[template_callback]
async fn create_room(&self) {
if !self.can_create_room() {
return;
}
let Some(session) = self.session() else {
return;
};
let imp = self.imp();
imp.create_button.set_is_loading(true);
imp.content.set_sensitive(false);
let name = Some(imp.room_name.text().trim())
.filter(|s| !s.is_empty())
.map(ToOwned::to_owned);
let buffer = imp.topic_text_view.buffer();
let (start_iter, end_iter) = buffer.bounds();
let topic = Some(buffer.text(&start_iter, &end_iter, false).trim())
.filter(|s| !s.is_empty())
.map(ToOwned::to_owned);
let mut request = assign!(
create_room::v3::Request::new(),
{
name,
topic,
}
);
if imp.visibility_private.is_active() {
// The room is private.
request.visibility = Visibility::Private;
if imp.encryption.is_active() {
let event =
InitialStateEvent::new(RoomEncryptionEventContent::with_recommended_defaults());
request.initial_state = vec![event.to_raw_any()];
}
} else {
// The room is public.
request.visibility = Visibility::Public;
request.room_alias_name = Some(imp.room_address.text().to_string());
}
let client = session.client();
let handle = spawn_tokio!(async move { client.create_room(request).await });
match handle.await.unwrap() {
Ok(matrix_room) => {
let Some(window) = self.root().and_downcast::<Window>() else {
return;
};
if let Some(room) = session.room_list().get_wait(matrix_room.room_id()).await {
window.session_view().select_room(room);
}
self.close();
}
Err(error) => {
error!("Could not create a new room: {error}");
self.handle_error(&error);
}
}
}
/// Display the error that occurred during creation.
fn handle_error(&self, error: &Error) {
let imp = self.imp();
imp.create_button.set_is_loading(false);
imp.content.set_sensitive(true);
// Handle the room address already taken error.
if let Some(kind) = error.client_api_error_kind() {
if *kind == ErrorKind::RoomInUse {
imp.room_address.add_css_class("error");
imp.room_address_error
.set_text(&gettext("The address is already taken."));
imp.room_address_error_revealer.set_reveal_child(true);
return;
}
}
toast!(self, error.to_user_facing());
}
/// Check whether a room can be created with the current input.
///
/// This will also change the UI elements to reflect why the room can't be
/// created.
fn can_create_room(&self) -> bool {
let imp = self.imp();
let mut can_create = true;
if imp.room_name.text().is_empty() {
can_create = false;
}
// Only public rooms have an address.
if imp.visibility_private.is_active() {
return can_create;
}
let room_address = imp.room_address.text();
// We don't allow #, : in the room address
let address_has_error = if room_address.contains(':') {
imp.room_address_error
.set_text(&gettext("Cannot contain “:”"));
can_create = false;
true
} else if room_address.contains('#') {
imp.room_address_error
.set_text(&gettext("Cannot contain “#”"));
can_create = false;
true
} else if room_address.len() > MAX_BYTES {
imp.room_address_error
.set_text(&gettext("Too long. Use a shorter address."));
can_create = false;
true
} else if room_address.is_empty() {
can_create = false;
false
} else {
false
};
if address_has_error {
imp.room_address.add_css_class("error");
} else {
imp.room_address.remove_css_class("error");
}
imp.room_address_error_revealer
.set_reveal_child(address_has_error);
can_create
}
/// Validate the form and change the corresponding UI elements.
#[template_callback]
fn validate_form(&self) {
self.imp()
.create_button
.set_sensitive(self.can_create_room());
}
}

10
src/session/view/session_view.rs

@ -3,7 +3,7 @@ use gtk::{gdk, glib, glib::clone, CompositeTemplate};
use ruma::{OwnedUserId, RoomId, RoomOrAliasId};
use tracing::{error, warn};
use super::{Content, CreateDmDialog, MediaViewer, RoomCreation, Sidebar};
use super::{Content, CreateDmDialog, CreateRoomDialog, MediaViewer, Sidebar};
use crate::{
components::{JoinRoomDialog, UserProfileDialog},
intent::SessionIntent,
@ -85,8 +85,8 @@ mod imp {
},
);
klass.install_action("session.room-creation", None, |obj, _, _| {
obj.imp().show_room_creation_dialog();
klass.install_action("session.create-room", None, |obj, _, _| {
obj.imp().create_room();
});
klass.install_action("session.show-join-room", None, |obj, _, _| {
@ -457,12 +457,12 @@ mod imp {
}
/// Show the dialog to create a room.
fn show_room_creation_dialog(&self) {
fn create_room(&self) {
let Some(session) = self.session.upgrade() else {
return;
};
let dialog = RoomCreation::new(&session);
let dialog = CreateRoomDialog::new(&session);
dialog.present(Some(&*self.obj()));
}

2
src/session/view/sidebar/mod.ui

@ -8,7 +8,7 @@
</item>
<item>
<attribute name="label" translatable="yes">_New Room…</attribute>
<attribute name="action">session.room-creation</attribute>
<attribute name="action">session.create-room</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Join Room…</attribute>

2
src/ui-resources.gresource.xml

@ -133,9 +133,9 @@
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/typing_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/content/room_history/verification_info_bar.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/create_dm_dialog/mod.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/create_room_dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/event_details_dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/media_viewer.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/room_creation.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/session_view.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/sidebar/icon_item_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">session/view/sidebar/mod.ui</file>

Loading…
Cancel
Save