session: Do not use Session to show toasts

This usually means we are trying to show a toast from a model Object,
which is not right.
This commit is contained in:
Kévin Commaille 2023-05-20 23:40:36 +02:00
parent e77fa4fedc
commit 4afba6e736
No known key found for this signature in database
GPG key ID: 29A48C1F03620416
19 changed files with 426 additions and 246 deletions

View file

@ -79,6 +79,7 @@ src/session/content/room_details/general_page/mod.rs
src/session/content/room_details/history_viewer/audio_row.rs
src/session/content/room_details/history_viewer/file_row.rs
src/session/content/room_details/invite_subpage/invitee_list.rs
src/session/content/room_details/invite_subpage/mod.rs
src/session/content/room_details/member_page/mod.rs
src/session/content/room_details/mod.rs
src/session/content/room_history/item_row.rs
@ -107,6 +108,8 @@ src/session/room_list.rs
src/session/sidebar/category/category_row.rs
src/session/sidebar/category/category_type.rs
src/session/sidebar/entry_type.rs
src/session/sidebar/room_row.rs
src/session/sidebar/row.rs
src/session/verification/identity_verification.rs
src/user_facing_error.rs
src/utils/media.rs

View file

@ -257,7 +257,9 @@ impl Login {
/// Drop the session and clean up its data from the system.
fn drop_session(&self) {
if let Some(session) = self.imp().session.take() {
glib::MainContext::default().block_on(session.logout());
glib::MainContext::default().block_on(async move {
let _ = session.logout().await;
});
}
}

View file

@ -169,7 +169,13 @@ impl DeactivateAccountSubpage {
match result {
Ok(_) => {
if let Some(session) = self.session() {
toast!(session, gettext("Account successfully deactivated"));
if let Some(window) = self
.root()
.and_downcast_ref::<gtk::Window>()
.and_then(|w| w.transient_for())
{
toast!(window, gettext("Account successfully deactivated"));
}
session.handle_logged_out();
}
self.activate_action("account-settings.close", None)

View file

@ -4,7 +4,7 @@ use gtk::{
CompositeTemplate,
};
use crate::{components::SpinnerButton, session::Session, spawn};
use crate::{components::SpinnerButton, session::Session, spawn, toast};
mod imp {
use glib::{subclass::InitializingObject, WeakRef};
@ -100,8 +100,11 @@ impl LogOutSubpage {
make_backup_button.set_sensitive(false);
spawn!(
clone!(@weak logout_button, @weak make_backup_button, @weak session => async move {
session.logout().await;
clone!(@weak self as obj, @weak logout_button, @weak make_backup_button, @weak session => async move {
if let Err(error) = session.logout().await {
toast!(obj, error);
}
logout_button.set_loading(false);
make_backup_button.set_sensitive(true);
})

View file

@ -1,5 +1,5 @@
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::ruma::{directory::PublicRoomsChunk, RoomId, RoomOrAliasId};
use matrix_sdk::ruma::directory::PublicRoomsChunk;
use crate::session::{room::Room, AvatarData, AvatarImage, AvatarUriSource, RoomList};
@ -181,14 +181,4 @@ impl PublicRoom {
pub fn matrix_public_room(&self) -> Option<&PublicRoomsChunk> {
self.imp().matrix_public_room.get()
}
pub fn join_or_view(&self) {
if let Some(room) = self.room() {
self.room_list().session().select_room(Some(room.clone()));
} else if let Some(matrix_public_room) = self.matrix_public_room() {
let room_id: &RoomId = matrix_public_room.room_id.as_ref();
self.room_list()
.join_by_id_or_alias(<&RoomOrAliasId>::from(room_id).to_owned(), vec![]);
}
}
}

View file

@ -5,6 +5,8 @@ use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate
use crate::{
components::{Avatar, Spinner, SpinnerButton},
session::content::explore::PublicRoom,
spawn, toast,
window::Window,
};
mod imp {
@ -79,9 +81,7 @@ mod imp {
self.parent_constructed();
self.button
.connect_clicked(clone!(@weak self as imp => move |_| {
if let Some(public_room) = &*imp.public_room.borrow() {
public_room.join_or_view();
};
imp.obj().join_or_view();
}));
}
@ -213,4 +213,27 @@ impl PublicRoomRow {
button.set_loading(public_room.is_pending());
}
/// Join or view the public room.
pub fn join_or_view(&self) {
let Some(public_room) = self.public_room() else {
return;
};
let room_list = public_room.room_list();
if let Some(room) = public_room.room() {
if let Some(window) = self.root().and_downcast::<Window>() {
let session = room_list.session();
window.show_room(session.session_id(), room.room_id());
}
} else if let Some(matrix_public_room) = public_room.matrix_public_room() {
let room_id = matrix_public_room.room_id.clone();
spawn!(clone!(@weak self as obj, @weak room_list => async move {
if let Err(error) = room_list.join_by_id_or_alias(room_id.into(), vec![]).await {
toast!(obj, error);
}
}));
}
}
}

View file

@ -1,11 +1,12 @@
use adw::subclass::prelude::*;
use gettextrs::gettext;
use gtk::{glib, glib::clone, prelude::*, CompositeTemplate};
use crate::{
components::{Avatar, LabelWithWidgets, Pill, SpinnerButton},
gettext_f,
session::room::{Room, RoomType},
spawn,
spawn, toast,
};
mod imp {
@ -217,6 +218,16 @@ impl Invite {
clone!(@weak self as obj, @strong room => move || async move {
let result = room.accept_invite().await;
if result.is_err() {
toast!(
obj,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to accept invitation for {room}. Try again later.",
),
@room,
);
obj.imp().accept_requests.borrow_mut().remove(&room);
obj.reset();
}
@ -239,6 +250,16 @@ impl Invite {
clone!(@weak self as obj, @strong room => move || async move {
let result = room.reject_invite().await;
if result.is_err() {
toast!(
obj,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to reject invitation for {room}. Try again later.",
),
@room,
);
obj.imp().reject_requests.borrow_mut().remove(&room);
obj.reset();
}

View file

@ -246,7 +246,7 @@ impl InviteeList {
if user.is_invited() && user.invite_exception().is_none() {
obj.add_invitee(user.clone());
} else {
obj.remove_invitee(user.user_id())
obj.remove_invitee(&user.user_id())
}
}),
);
@ -365,8 +365,8 @@ impl InviteeList {
.collect()
}
fn remove_invitee(&self, user_id: OwnedUserId) {
let removed = self.imp().invitee_list.borrow_mut().remove(&user_id);
pub fn remove_invitee(&self, user_id: &UserId) {
let removed = self.imp().invitee_list.borrow_mut().remove(user_id);
if let Some(user) = removed {
user.set_invited(false);
self.emit_by_name::<()>("invitee-removed", &[&user]);

View file

@ -1,4 +1,5 @@
use adw::subclass::prelude::*;
use gettextrs::ngettext;
use gtk::{gdk, glib, glib::clone, prelude::*, CompositeTemplate};
mod invitee;
@ -11,8 +12,8 @@ use self::{
};
use crate::{
components::{Pill, Spinner, SpinnerButton},
session::{Room, User},
spawn,
session::{Room, User, UserExt},
spawn, toast,
};
mod imp {
@ -297,9 +298,16 @@ impl InviteSubpage {
.ok()
}
/// Invite the selected users to the room.
fn invite(&self) {
self.imp().invite_button.set_loading(true);
spawn!(clone!(@weak self as obj => async move {
obj.invite_inner().await;
}));
}
async fn invite_inner(&self) {
let Some(room) = self.room() else {
return;
};
@ -312,11 +320,38 @@ impl InviteSubpage {
.into_iter()
.map(glib::object::Cast::upcast)
.collect();
spawn!(clone!(@weak self as obj => async move {
room.invite(invitees.as_slice()).await;
obj.close();
obj.imp().invite_button.set_loading(false);
}));
match room.invite(&invitees).await {
Ok(()) => {
self.close();
}
Err(failed_users) => {
for invitee in &invitees {
if !failed_users.contains(&invitee) {
user_list.remove_invitee(&invitee.user_id())
}
}
let n = failed_users.len();
let first_failed = failed_users.first().unwrap();
toast!(
self,
ngettext(
// Translators: Do NOT translate the content between '{' and '}', these
// are variable names.
"Failed to invite {user} to {room}. Try again later.",
"Failed to invite {n} users to {room}. Try again later.",
n as u32,
),
@user = first_failed,
@room,
n = n.to_string(),
);
}
}
self.imp().invite_button.set_loading(false);
}
fn update_view(&self) {

View file

@ -178,8 +178,10 @@ mod imp {
widget.send_text_message();
},
);
klass.install_action("room-history.leave", None, move |widget, _, _| {
widget.leave();
klass.install_action("room-history.leave", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.leave().await;
}));
});
klass.install_action("room-history.try-again", None, move |widget, _, _| {
@ -779,9 +781,24 @@ impl RoomHistory {
self.clear_related_event();
}
pub fn leave(&self) {
if let Some(room) = &*self.imp().room.borrow() {
room.set_category(RoomType::Left);
/// Leave the room.
pub async fn leave(&self) {
let Some(room) = self.room() else {
return;
};
let previous_category = room.category();
if room.set_category(RoomType::Left).await.is_err() {
toast!(
self,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
"Failed to move {room} from {previous_category} to {new_category}.",
),
@room,
previous_category = previous_category.to_string(),
new_category = RoomType::Left.to_string(),
);
}
}
@ -1440,10 +1457,16 @@ impl RoomHistory {
let Some(successor) = room.successor() else {
return;
};
let successor = successor.to_owned();
room.session()
.room_list()
.join_by_id_or_alias(successor.to_owned().into(), vec![]);
spawn!(clone!(@weak self as obj, @weak room => async move {
if let Err(error) = room.session()
.room_list()
.join_by_id_or_alias(successor.into(), vec![]).await
{
toast!(obj, error);
}
}));
}
}
}

View file

@ -2,7 +2,7 @@ use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, glib::clone, CompositeTemplate};
use crate::{session::Room, utils::BoundObjectWeakRef};
use crate::{session::Room, spawn, toast, utils::BoundObjectWeakRef};
mod imp {
use glib::subclass::InitializingObject;
@ -133,9 +133,20 @@ impl StateTombstone {
let Some(successor) = room.successor() else {
return;
};
let session = room.session();
let room_list = session.room_list();
room.session()
.room_list()
.join_or_view(successor.into(), vec![]);
// Join or view the room with the given identifier.
if let Some(successor_room) = room_list.joined_room(successor.into()) {
session.select_room(Some(successor_room));
} else {
let successor = successor.to_owned();
spawn!(clone!(@weak self as obj, @weak room_list => async move {
if let Err(error) = room_list.join_by_id_or_alias(successor.into(), vec![]).await {
toast!(obj, error);
}
}));
}
}
}

View file

@ -103,7 +103,7 @@ impl DmUser {
////
/// If A DM chat exists already no new room is created and the existing one
/// is returned.
pub async fn start_chat(&self) -> Result<Room, matrix_sdk::Error> {
pub async fn start_chat(&self) -> Result<Room, ()> {
let session = self.session();
let client = session.client();
let other_user = self.user_id();
@ -116,8 +116,11 @@ impl DmUser {
// We can be sure that this room has only ourself and maybe the other user as
// member.
if room.matrix_room().active_members_count() < 2 {
room.invite(&[self.clone().upcast()]).await;
debug!("{other_user} left the chat, re-invite them");
if room.invite(&[self.clone().upcast()]).await.is_err() {
return Err(());
}
}
return Ok(room);
@ -137,7 +140,7 @@ impl DmUser {
}
Err(error) => {
error!("Couldnt create a new Direct Chat: {error}");
Err(error)
Err(())
}
}
}

View file

@ -2,10 +2,11 @@ use adw::subclass::prelude::*;
use gtk::{gdk, glib, glib::clone, prelude::*, CompositeTemplate};
mod dm_user;
use self::dm_user::DmUser;
mod dm_user_list;
mod dm_user_row;
use self::{
dm_user::DmUser,
dm_user_list::{DmUserList, DmUserListState},
dm_user_row::DmUserRow,
};
@ -177,23 +178,32 @@ impl CreateDmDialog {
#[template_callback]
fn row_activated_cb(&self, row: gtk::ListBoxRow) {
let Some(user): Option<DmUser> = row.downcast::<DmUserRow>().ok().and_then(|r| r.user()) else { return; };
let Some(user) = row.downcast_ref::<DmUserRow>().and_then(|r| r.user()) else {
return;
};
// TODO: For now we show the loading page while we create the room,
// ideally we would like to have the same behavior as Element:
// Create the room only once the user sends a message
self.imp().stack.set_visible_child_name("loading-page");
self.imp().search_entry.set_sensitive(false);
let imp = self.imp();
imp.stack.set_visible_child_name("loading-page");
imp.search_entry.set_sensitive(false);
spawn!(clone!(@weak self as obj, @weak user => async move {
match user.start_chat().await {
Ok(room) => {
user.session().select_room(Some(room));
obj.close();
}
Err(_) => {
obj.show_error(&gettext("Failed to create a new Direct Chat"));
}
}
obj.start_chat(&user).await;
}));
}
async fn start_chat(&self, user: &DmUser) {
match user.start_chat().await {
Ok(room) => {
user.session().select_room(Some(room));
self.close();
}
Err(_) => {
self.show_error(&gettext("Failed to create a new Direct Chat"));
self.imp().search_entry.set_sensitive(true);
}
}
}
}

View file

@ -1,12 +1,12 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{gdk, glib, CompositeTemplate};
use gtk::{gdk, glib, glib::clone, CompositeTemplate};
use ruma::{
matrix_uri::MatrixId, MatrixToUri, MatrixUri, OwnedRoomOrAliasId, OwnedServerName,
RoomOrAliasId,
};
use crate::session::Session;
use crate::{session::Session, spawn, toast};
mod imp {
use glib::{object::WeakRef, subclass::InitializingObject};
@ -148,15 +148,25 @@ impl JoinRoomDialog {
/// Join the room that was entered, if it is valid.
fn join_room(&self) {
let Some(session) = self.session() else {
return;
};
let Some((room_id, via)) = parse_room(&self.imp().entry.text()) else {
return;
};
session.room_list().join_or_view((&*room_id).into(), via);
let Some(session) = self.session() else {
return;
};
let room_list = session.room_list();
// Join or view the room with the given identifier.
if let Some(room) = room_list.joined_room((&*room_id).into()) {
session.select_room(Some(room));
} else {
spawn!(clone!(@weak self as obj, @weak room_list => async move {
if let Err(error) = room_list.join_by_id_or_alias(room_id, via).await {
toast!(obj, error);
}
}));
}
}
}

View file

@ -64,7 +64,7 @@ pub use self::{
use crate::{
secret::StoredSession,
session::sidebar::ItemList,
spawn, spawn_tokio, toast,
spawn, spawn_tokio,
utils::{
check_if_reachable,
matrix::{self, ClientSetupError},
@ -681,7 +681,7 @@ impl Session {
dialog.present();
}
pub async fn logout(&self) {
pub async fn logout(&self) -> Result<(), String> {
let stack = &self.imp().stack;
debug!("The session is about to be logged out");
@ -698,10 +698,15 @@ impl Session {
});
match handle.await.unwrap() {
Ok(_) => self.cleanup_session().await,
Ok(_) => {
self.cleanup_session().await;
Ok(())
}
Err(error) => {
error!("Couldnt logout the session: {error}");
toast!(self, gettext("Failed to logout the session."));
Err(gettext("Failed to logout the session."))
}
}
}

View file

@ -10,7 +10,7 @@ mod typing_list;
use std::{cell::RefCell, io::Cursor};
use gettextrs::{gettext, ngettext};
use gettextrs::gettext;
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use log::{debug, error, warn};
use matrix_sdk::{
@ -58,7 +58,7 @@ use crate::{
sidebar::{SidebarItem, SidebarItemImpl},
AvatarData, AvatarImage, AvatarUriSource, Session, User,
},
spawn, spawn_tokio, toast,
spawn, spawn_tokio,
};
mod imp {
@ -143,7 +143,7 @@ mod imp {
.read_only()
.build(),
glib::ParamSpecEnum::builder::<RoomType>("category")
.explicit_notify()
.read_only()
.build(),
glib::ParamSpecString::builder("topic").read_only().build(),
glib::ParamSpecUInt64::builder("latest-unread")
@ -184,7 +184,6 @@ mod imp {
match pspec.name() {
"session" => self.session.set(value.get().ok().as_ref()),
"category" => obj.set_category(value.get().unwrap()),
"room-id" => self
.room_id
.set(RoomId::parse(value.get::<&str>().unwrap()).unwrap())
@ -299,18 +298,13 @@ impl Room {
self.imp().room_id.get().unwrap()
}
/// Set the proper category for this joined room.
pub fn set_joined(&self) {
/// Whether this room is direct or not.
pub async fn is_direct(&self) -> bool {
let matrix_room = self.matrix_room();
let handle = spawn_tokio!(async move { matrix_room.is_direct().await.unwrap_or_default() });
spawn!(clone!(@weak self as obj => async move {
if handle.await.unwrap() {
obj.set_category(RoomType::Direct);
} else {
obj.set_category(RoomType::Normal);
}
}));
spawn_tokio!(async move { matrix_room.is_direct().await.unwrap_or_default() })
.await
.unwrap()
}
pub fn matrix_room(&self) -> MatrixRoom {
@ -346,10 +340,10 @@ impl Room {
}
/// Forget a room that is left.
pub fn forget(&self) {
pub async fn forget(&self) -> MatrixResult<()> {
if self.category() != RoomType::Left {
warn!("Cannot forget a room that is not left");
return;
return Ok(());
}
let matrix_room = self.matrix_room();
@ -361,29 +355,20 @@ impl Room {
}
});
spawn!(
glib::PRIORITY_DEFAULT_IDLE,
clone!(@weak self as obj => async move {
match handle.await.unwrap() {
Ok(_) => {
obj.emit_by_name::<()>("room-forgotten", &[]);
}
Err(error) => {
error!("Couldnt forget the room: {error}");
match handle.await.unwrap() {
Ok(_) => {
self.emit_by_name::<()>("room-forgotten", &[]);
Ok(())
}
Err(error) => {
error!("Couldnt forget the room: {error}");
toast!(
obj.session(),
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
gettext("Failed to forget {room}."),
@room = &obj,
);
// Load the previous category
self.load_category();
// Load the previous category
obj.load_category();
},
};
})
);
Err(error)
}
}
}
pub fn is_joined(&self) -> bool {
@ -417,32 +402,34 @@ impl Room {
///
/// Note: Rooms can't be moved to the invite category and they can't be
/// moved once they are upgraded.
pub fn set_category(&self, category: RoomType) {
pub async fn set_category(&self, category: RoomType) -> MatrixResult<()> {
let matrix_room = self.matrix_room();
let previous_category = self.category();
if previous_category == category {
return;
return Ok(());
}
if previous_category == RoomType::Outdated {
warn!("Can't set the category of an upgraded room");
return;
return Ok(());
}
match category {
RoomType::Invited => {
warn!("Rooms cant be moved to the invite Category");
return;
return Ok(());
}
RoomType::Outdated => {
// Outdated rooms don't need to propagate anything to the server
self.set_category_internal(category);
return;
return Ok(());
}
_ => {}
}
self.set_category_internal(category);
let handle = spawn_tokio!(async move {
match matrix_room {
MatrixRoom::Invited(room) => match category {
@ -615,33 +602,17 @@ impl Room {
Result::<_, matrix_sdk::Error>::Ok(())
});
spawn!(
glib::PRIORITY_DEFAULT_IDLE,
clone!(@weak self as obj => async move {
match handle.await.unwrap() {
Ok(_) => {},
Err(error) => {
error!("Couldnt set the room category: {error}");
match handle.await.unwrap() {
Ok(_) => Ok(()),
Err(error) => {
error!("Couldnt set the room category: {error}");
toast!(
obj.session(),
gettext(
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
"Failed to move {room} from {previous_category} to {new_category}.",
),
@room = obj,
previous_category = previous_category.to_string(),
new_category = category.to_string(),
);
// Load the previous category
self.load_category();
// Load the previous category
obj.load_category();
},
};
})
);
self.set_category_internal(category);
Err(error)
}
}
}
pub fn load_category(&self) {
@ -1303,17 +1274,6 @@ impl Room {
Ok(_) => Ok(()),
Err(error) => {
error!("Accepting invitation failed: {error}");
toast!(
self.session(),
gettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to accept invitation for {room}. Try again later.",
),
@room = self,
);
Err(error)
}
}
@ -1333,16 +1293,6 @@ impl Room {
Err(error) => {
error!("Rejecting invitation failed: {error}");
toast!(
self.session(),
gettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to reject invitation for {room}. Try again later.",
),
@room = self,
);
Err(error)
}
}
@ -1525,21 +1475,25 @@ impl Room {
});
}
pub async fn invite(&self, users: &[User]) {
/// Invite the given users to this room.
///
/// Returns `Ok(())` if all the invites are sent successfully, otherwise
/// returns the list of users who could not be invited.
pub async fn invite<'a>(&self, users: &'a [User]) -> Result<(), Vec<&'a User>> {
let MatrixRoom::Joined(matrix_room) = self.matrix_room() else {
error!("Cant invite users, because this room isnt a joined room");
return;
return Ok(());
};
let user_ids: Vec<OwnedUserId> = users.iter().map(|user| user.user_id()).collect();
let handle = spawn_tokio!(async move {
let invitiations = user_ids
let invitations = user_ids
.iter()
.map(|user_id| matrix_room.invite_user_by_id(user_id));
futures::future::join_all(invitiations).await
futures::future::join_all(invitations).await
});
let mut failed_invites: Vec<User> = Vec::new();
let mut failed_invites = Vec::new();
for (index, result) in handle.await.unwrap().iter().enumerate() {
match result {
Ok(_) => {}
@ -1548,43 +1502,15 @@ impl Room {
"Failed to invite user with id {}: {error}",
users[index].user_id(),
);
failed_invites.push(users[index].clone());
failed_invites.push(&users[index]);
}
}
}
if !failed_invites.is_empty() {
let no_failed = failed_invites.len();
let first_failed = failed_invites.first().unwrap();
// TODO: should we show all the failed users?
if no_failed == 1 {
toast!(
self.session(),
gettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to invite {user} to {room}. Try again later.",
),
@user = first_failed,
@room = self,
);
} else {
let n = (no_failed - 1) as u32;
toast!(
self.session(),
ngettext(
// Translators: Do NOT translate the content between '{' and '}', this
// is a variable name.
"Failed to invite {user} and 1 other user to {room}. Try again later.",
"Failed to invite {user} and {n} other users to {room}. Try again later.",
n,
),
@user = first_failed,
@room = self,
n = n.to_string(),
);
};
if failed_invites.is_empty() {
Ok(())
} else {
Err(failed_invites)
}
}

View file

@ -14,7 +14,7 @@ use matrix_sdk::{
use crate::{
gettext_f,
session::{room::Room, Session},
spawn, spawn_tokio, toast,
spawn_tokio,
};
mod imp {
@ -314,7 +314,11 @@ impl RoomList {
}
/// Join the room with the given identifier.
pub fn join_by_id_or_alias(&self, identifier: OwnedRoomOrAliasId, via: Vec<OwnedServerName>) {
pub async fn join_by_id_or_alias(
&self,
identifier: OwnedRoomOrAliasId,
via: Vec<OwnedServerName>,
) -> Result<(), String> {
let client = self.session().client();
let identifier_clone = identifier.clone();
@ -326,26 +330,25 @@ impl RoomList {
.await
});
spawn!(
glib::PRIORITY_DEFAULT_IDLE,
clone!(@weak self as obj => async move {
match handle.await.unwrap() {
Ok(matrix_room) => obj.pending_rooms_replace_or_remove(&identifier, matrix_room.room_id()),
Err(error) => {
obj.pending_rooms_remove(&identifier);
error!("Joining room {identifier} failed: {error}");
match handle.await.unwrap() {
Ok(matrix_room) => {
self.pending_rooms_replace_or_remove(&identifier, matrix_room.room_id());
Ok(())
}
Err(error) => {
self.pending_rooms_remove(&identifier);
error!("Joining room {identifier} failed: {error}");
let error = gettext_f(
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
"Failed to join room {room_name}. Try again later.",
&[("room_name", identifier.as_str())]
);
let error = gettext_f(
// Translators: Do NOT translate the content between '{' and '}', this is a
// variable name.
"Failed to join room {room_name}. Try again later.",
&[("room_name", identifier.as_str())],
);
toast!(obj.session(), error);
}
}
})
);
Err(error)
}
}
}
pub fn connect_pending_rooms_changed<F: Fn(&Self) + 'static>(
@ -367,15 +370,6 @@ impl RoomList {
.filter(|room| room.is_joined())
}
/// Join or view the room with the given identifier.
pub fn join_or_view(&self, identifier: RoomIdentifier, via: Vec<OwnedServerName>) {
if let Some(room) = self.joined_room(identifier) {
self.session().select_room(Some(room));
} else {
self.join_by_id_or_alias(identifier.into(), via);
}
}
/// Add a room that was tombstoned but for which we haven't joined the
/// successor yet.
pub fn add_tombstoned_room(&self, room_id: OwnedRoomId) {

View file

@ -1,10 +1,12 @@
use adw::subclass::prelude::BinImpl;
use gettextrs::gettext;
use gtk::{gdk, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
use super::Row;
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl},
session::room::{HighlightFlags, Room, RoomType},
spawn, toast,
};
mod imp {
@ -39,34 +41,52 @@ mod imp {
klass.set_accessible_role(gtk::AccessibleRole::Group);
klass.install_action("room-row.accept-invite", None, move |widget, _, _| {
widget.set_room_as_normal_or_direct();
klass.install_action("room-row.accept-invite", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_room_as_normal_or_direct().await;
}));
});
klass.install_action("room-row.reject-invite", None, move |widget, _, _| {
widget.room().unwrap().set_category(RoomType::Left)
klass.install_action("room-row.reject-invite", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_category(RoomType::Left).await
}));
});
klass.install_action("room-row.set-favorite", None, move |widget, _, _| {
widget.room().unwrap().set_category(RoomType::Favorite);
klass.install_action("room-row.set-favorite", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_category(RoomType::Favorite).await
}));
});
klass.install_action("room-row.set-normal", None, move |widget, _, _| {
widget.room().unwrap().set_category(RoomType::Normal);
klass.install_action("room-row.set-normal", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_category(RoomType::Normal).await
}));
});
klass.install_action("room-row.set-lowpriority", None, move |widget, _, _| {
widget.room().unwrap().set_category(RoomType::LowPriority);
klass.install_action("room-row.set-lowpriority", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_category(RoomType::LowPriority).await
}));
});
klass.install_action("room-row.set-direct", None, move |widget, _, _| {
widget.room().unwrap().set_category(RoomType::Direct);
klass.install_action("room-row.set-direct", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_category(RoomType::Direct).await
}));
});
klass.install_action("room-row.leave", None, move |widget, _, _| {
widget.room().unwrap().set_category(RoomType::Left);
klass.install_action("room-row.leave", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_category(RoomType::Left).await
}));
});
klass.install_action("room-row.join", None, move |widget, _, _| {
widget.set_room_as_normal_or_direct();
klass.install_action("room-row.join", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.set_room_as_normal_or_direct().await;
}));
});
klass.install_action("room-row.forget", None, move |widget, _, _| {
widget.room().unwrap().forget();
klass.install_action("room-row.forget", None, move |obj, _, _| {
spawn!(clone!(@weak obj => async move {
obj.forget().await
}));
});
}
@ -350,7 +370,66 @@ impl RoomRow {
row.remove_css_class("drag");
}
fn set_room_as_normal_or_direct(&self) {
self.room().unwrap().set_joined();
async fn set_room_as_normal_or_direct(&self) {
let Some(room) = self.room() else {
return;
};
let previous_category = room.category();
let category = if room.is_direct().await {
RoomType::Direct
} else {
RoomType::Normal
};
if room.set_category(category).await.is_err() {
toast!(
self,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
"Failed to move {room} from {previous_category} to {new_category}.",
),
@room,
previous_category = previous_category.to_string(),
new_category = category.to_string(),
);
}
}
/// Change the category of this room.
async fn set_category(&self, category: RoomType) {
let Some(room) = self.room() else {
return;
};
let previous_category = room.category();
if room.set_category(category).await.is_err() {
toast!(
self,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
"Failed to move {room} from {previous_category} to {new_category}.",
),
@room,
previous_category = previous_category.to_string(),
new_category = category.to_string(),
);
}
}
/// Forget this room.
async fn forget(&self) {
let Some(room) = self.room() else {
return;
};
if room.forget().await.is_err() {
toast!(
self,
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
gettext("Failed to forget {room}."),
@room,
);
}
}
}

View file

@ -1,4 +1,5 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{gdk, glib, glib::clone};
use super::{CategoryType, EntryType};
@ -10,6 +11,7 @@ use crate::{
},
verification::IdentityVerification,
},
spawn, toast,
utils::BoundObjectWeakRef,
};
@ -312,12 +314,16 @@ impl Row {
if let Ok(room) = value.get::<Room>() {
if let Some(target_type) = self.room_type() {
if room.category().can_change_to(target_type) {
room.set_category(target_type);
spawn!(clone!(@weak self as obj, @weak room => async move {
obj.set_room_category(&room, target_type).await;
}));
ret = true;
}
} else if let Some(entry_type) = self.entry_type() {
if room.category() == RoomType::Left && entry_type == EntryType::Forget {
room.forget();
spawn!(clone!(@weak self as obj, @weak room => async move {
obj.forget_room(&room).await;
}));
ret = true;
}
}
@ -326,6 +332,36 @@ impl Row {
ret
}
/// Change the category of the given room room.
async fn set_room_category(&self, room: &Room, category: RoomType) {
let previous_category = room.category();
if room.set_category(category).await.is_err() {
toast!(
self,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
"Failed to move {room} from {previous_category} to {new_category}.",
),
@room,
previous_category = previous_category.to_string(),
new_category = category.to_string(),
);
}
}
/// Forget the given room.
async fn forget_room(&self, room: &Room) {
if room.forget().await.is_err() {
toast!(
self,
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
gettext("Failed to forget {room}."),
@room,
);
}
}
/// Update the disabled or empty state of this drop target.
fn update_for_drop_source_type(&self) {
let source_type = self.sidebar().drop_source_type();