[fractal/fractal-next] content: Add invitation widget and implement accept/reject
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] content: Add invitation widget and implement accept/reject
- Date: Mon, 24 May 2021 14:16:36 +0000 (UTC)
commit fc5f97448ab80d19a9ca497549ca3fd9c0cf42ee
Author: Julian Sparber <julian sparber net>
Date: Fri May 21 19:50:47 2021 +0200
content: Add invitation widget and implement accept/reject
To fully work this will need some work on the sdk side, since the
inviter isn't available after dropping a SyncResponse, therefore after a
restart the inviter is forgotten. Also the display name isn't shown
always correclty because the sdk doesn't calculate it correclty.
data/resources/resources.gresource.xml | 1 +
data/resources/style.css | 4 +
data/resources/ui/content-invite.ui | 138 +++++++++++++++++
data/resources/ui/content.ui | 10 +-
po/POTFILES.in | 2 +
src/meson.build | 1 +
src/session/content/content.rs | 43 +++++-
src/session/content/invite.rs | 261 +++++++++++++++++++++++++++++++++
src/session/content/mod.rs | 2 +
src/session/mod.rs | 21 ++-
src/session/room/room.rs | 83 ++++++++++-
src/session/user.rs | 39 ++++-
12 files changed, 596 insertions(+), 9 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 0d47f3e8..7e7b6a2b 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -9,6 +9,7 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="content-divider-row.ui">ui/content-divider-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-state-row.ui">ui/content-state-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="content-markdown-popover.ui">ui/content-markdown-popover.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="content-invite.ui">ui/content-invite.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="session.ui">ui/session.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="sidebar.ui">ui/sidebar.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 3f8b404e..b4e3ba64 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -145,3 +145,7 @@ headerbar.flat {
border: 2px solid @theme_selected_bg_color;
padding: 5px;
}
+
+.invite-room-name {
+ font-size: 24px;
+}
diff --git a/data/resources/ui/content-invite.ui b/data/resources/ui/content-invite.ui
new file mode 100644
index 00000000..4902d65b
--- /dev/null
+++ b/data/resources/ui/content-invite.ui
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ContentInvite" parent="AdwBin">
+ <property name="vexpand">True</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="AdwHeaderBar" id="headerbar">
+ <property name="show-start-title-buttons" bind-source="ContentInvite" bind-property="compact"
bind-flags="sync-create"/>
+ <child type="start">
+ <object class="GtkButton" id="back">
+ <property name="visible" bind-source="ContentInvite" bind-property="compact"
bind-flags="sync-create"/>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="action-name">content.go-back</property>
+ </object>
+ </child>
+ <child type="title">
+ <object class="AdwWindowTitle">
+ <property name="title" translatable="yes">Invite</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="vexpand">True</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="child">
+ <object class="AdwClamp">
+ <property name="maximum-size">400</property>
+ <property name="tightening-threshold">200</property>
+ <property name="vexpand">True</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">24</property>
+ <property name="margin-start">24</property>
+ <property name="margin-end">24</property>
+ <property name="child">
+ <object class="GtkBox">
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <property name="spacing">24</property>
+ <property name="orientation">vertical</property>
+ <accessibility>
+ <property name="label" translatable="yes">Invite</property>
+ </accessibility>
+ <child>
+ <object class="AdwAvatar">
+ <property name="show-initials">True</property>
+ <property name="size">150</property>
+ <property name="text" bind-source="display_name" bind-property="label"
bind-flags="sync-create"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="display_name">
+ <property name="ellipsize">end</property>
+ <binding name="label">
+ <lookup name="display-name">
+ <lookup name="room">ContentInvite</lookup>
+ </lookup>
+ </binding>
+ <style>
+ <class name="invite-room-name"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="room_topic">
+ <property name="wrap">True</property>
+ <property name="justify">center</property>
+ <binding name="label">
+ <lookup name="topic">
+ <lookup name="room">ContentInvite</lookup>
+ </lookup>
+ </binding>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="halign">center</property>
+ <child>
+ <object class="UserPill" id="inviter">
+ <binding name="user">
+ <lookup name="inviter">
+ <lookup name="room">ContentInvite</lookup>
+ </lookup>
+ </binding>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <!-- Translators: the space at the beginning is there on purpose -->
+ <property name="label" translatable="yes"> invited you</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="halign">center</property>
+ <property name="spacing">24</property>
+ <property name="margin-top">24</property>
+ <child>
+ <object class="SpinnerButton" id="reject_button">
+ <property name="label" translatable="yes">_Reject</property>
+ <property name="action-name">invite.reject</property>
+ <style>
+ <class name="pill-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="SpinnerButton" id="accept_button">
+ <property name="label" translatable="yes">_Accept</property>
+ <property name="action-name">invite.accept</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="pill-button"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
+
diff --git a/data/resources/ui/content.ui b/data/resources/ui/content.ui
index 332a6054..f30599b8 100644
--- a/data/resources/ui/content.ui
+++ b/data/resources/ui/content.ui
@@ -4,9 +4,15 @@
<property name="vexpand">True</property>
<property name="hexpand">True</property>
<property name="child">
- <object class="GtkStack">
+ <object class="GtkStack" id="stack">
<child>
- <object class="ContentRoomHistory">
+ <object class="ContentRoomHistory" id="room_history">
+ <property name="compact" bind-source="Content" bind-property="compact" bind-flags="sync-create"/>
+ <property name="room" bind-source="Content" bind-property="room" bind-flags="sync-create"/>
+ </object>
+ </child>
+ <child>
+ <object class="ContentInvite" id="invite">
<property name="compact" bind-source="Content" bind-property="compact" bind-flags="sync-create"/>
<property name="room" bind-source="Content" bind-property="room" bind-flags="sync-create"/>
</object>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1e51db6c..9ad215dd 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,7 @@ data/org.gnome.FractalNext.metainfo.xml.in.in
data/resources/ui/content-divider-row.ui
data/resources/ui/content-item-row-menu.ui
data/resources/ui/content-item.ui
+data/resources/ui/content-invite.ui
data/resources/ui/content-markdown-popover.ui
data/resources/ui/content-message-row.ui
data/resources/ui/content-room-history.ui
@@ -41,6 +42,7 @@ src/session/categories/mod.rs
src/session/content/content.rs
src/session/content/divider_row.rs
src/session/content/item_row.rs
+src/session/content/invite.rs
src/session/content/markdown_popover.rs
src/session/content/message_row.rs
src/session/content/mod.rs
diff --git a/src/meson.build b/src/meson.build
index 5a53a121..38b184fa 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -39,6 +39,7 @@ sources = files(
'session/content/content.rs',
'session/content/divider_row.rs',
'session/content/item_row.rs',
+ 'session/content/invite.rs',
'session/content/markdown_popover.rs',
'session/content/message_row.rs',
'session/content/mod.rs',
diff --git a/src/session/content/content.rs b/src/session/content/content.rs
index 901dce20..af97a437 100644
--- a/src/session/content/content.rs
+++ b/src/session/content/content.rs
@@ -1,10 +1,10 @@
-use crate::session::{content::RoomHistory, room::Room};
+use crate::session::{categories::CategoryType, content::Invite, content::RoomHistory, room::Room};
use adw::subclass::prelude::*;
-use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
mod imp {
use super::*;
- use glib::subclass::InitializingObject;
+ use glib::{signal::SignalHandlerId, subclass::InitializingObject};
use std::cell::{Cell, RefCell};
#[derive(Debug, Default, CompositeTemplate)]
@@ -12,6 +12,13 @@ mod imp {
pub struct Content {
pub compact: Cell<bool>,
pub room: RefCell<Option<Room>>,
+ pub category_handler: RefCell<Option<SignalHandlerId>>,
+ #[template_child]
+ pub stack: TemplateChild<gtk::Stack>,
+ #[template_child]
+ pub room_history: TemplateChild<RoomHistory>,
+ #[template_child]
+ pub invite: TemplateChild<Invite>,
}
#[glib::object_subclass]
@@ -22,6 +29,7 @@ mod imp {
fn class_init(klass: &mut Self::Class) {
RoomHistory::static_type();
+ Invite::static_type();
Self::bind_template(klass);
klass.set_accessible_role(gtk::AccessibleRole::Group);
@@ -110,7 +118,26 @@ impl Content {
return;
}
+ if let Some(category_handler) = priv_.category_handler.take() {
+ if let Some(room) = self.room() {
+ room.disconnect(category_handler);
+ }
+ }
+
+ if let Some(ref room) = room {
+ let handler_id = room.connect_notify_local(
+ Some("category"),
+ clone!(@weak self as obj => move |room, _| {
+ obj.set_visible_child(room);
+ }),
+ );
+
+ self.set_visible_child(&room);
+ priv_.category_handler.replace(Some(handler_id));
+ }
+
priv_.room.replace(room);
+
self.notify("room");
}
@@ -118,4 +145,14 @@ impl Content {
let priv_ = imp::Content::from_instance(self);
priv_.room.borrow().clone()
}
+
+ fn set_visible_child(&self, room: &Room) {
+ let priv_ = imp::Content::from_instance(self);
+
+ if room.category() == CategoryType::Invited {
+ priv_.stack.set_visible_child(&*priv_.invite);
+ } else {
+ priv_.stack.set_visible_child(&*priv_.room_history);
+ }
+ }
}
diff --git a/src/session/content/invite.rs b/src/session/content/invite.rs
new file mode 100644
index 00000000..3b3fa2c8
--- /dev/null
+++ b/src/session/content/invite.rs
@@ -0,0 +1,261 @@
+use crate::{
+ components::{SpinnerButton, UserPill},
+ session::{categories::CategoryType, room::Room},
+};
+use adw::subclass::prelude::*;
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use gtk_macros::spawn;
+use log::error;
+
+mod imp {
+ use super::*;
+ use glib::{signal::SignalHandlerId, subclass::InitializingObject};
+ use std::cell::{Cell, RefCell};
+ use std::collections::HashSet;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/content-invite.ui")]
+ pub struct Invite {
+ pub compact: Cell<bool>,
+ pub room: RefCell<Option<Room>>,
+ pub accept_requests: RefCell<HashSet<Room>>,
+ pub reject_requests: RefCell<HashSet<Room>>,
+ pub category_handler: RefCell<Option<SignalHandlerId>>,
+ #[template_child]
+ pub headerbar: TemplateChild<adw::HeaderBar>,
+ #[template_child]
+ pub inviter: TemplateChild<adw::HeaderBar>,
+ #[template_child]
+ pub room_topic: TemplateChild<gtk::Label>,
+ #[template_child]
+ pub accept_button: TemplateChild<SpinnerButton>,
+ #[template_child]
+ pub reject_button: TemplateChild<SpinnerButton>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for Invite {
+ const NAME: &'static str = "ContentInvite";
+ type Type = super::Invite;
+ type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ UserPill::static_type();
+ SpinnerButton::static_type();
+ Self::bind_template(klass);
+ klass.set_accessible_role(gtk::AccessibleRole::Group);
+
+ klass.install_action("invite.reject", None, move |widget, _, _| {
+ widget.reject();
+ });
+ klass.install_action("invite.accept", None, move |widget, _, _| {
+ widget.accept();
+ });
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for Invite {
+ fn properties() -> &'static [glib::ParamSpec] {
+ use once_cell::sync::Lazy;
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpec::new_boolean(
+ "compact",
+ "Compact",
+ "Wheter a compact view is used or not",
+ false,
+ glib::ParamFlags::READWRITE,
+ ),
+ glib::ParamSpec::new_object(
+ "room",
+ "Room",
+ "The room currently shown",
+ Room::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "compact" => {
+ let compact = value.get().unwrap();
+ self.compact.set(compact);
+ }
+ "room" => {
+ let room = value.get().unwrap();
+ obj.set_room(room);
+ }
+
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "compact" => self.compact.get().to_value(),
+ "room" => obj.room().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ self.room_topic
+ .connect_notify_local(Some("label"), |room_topic, _| {
+ room_topic.set_visible(!room_topic.label().is_empty());
+ });
+
+ self.room_topic
+ .set_visible(!self.room_topic.label().is_empty());
+ }
+ }
+
+ impl WidgetImpl for Invite {}
+ impl BinImpl for Invite {}
+}
+
+glib::wrapper! {
+ pub struct Invite(ObjectSubclass<imp::Invite>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl Invite {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create Invite")
+ }
+
+ pub fn set_room(&self, room: Option<Room>) {
+ let priv_ = imp::Invite::from_instance(self);
+
+ if self.room() == room {
+ return;
+ }
+
+ match room {
+ Some(ref room) if priv_.accept_requests.borrow().contains(room) => {
+ self.action_set_enabled("invite.accept", false);
+ self.action_set_enabled("invite.reject", false);
+ priv_.accept_button.set_loading(true);
+ }
+ Some(ref room) if priv_.reject_requests.borrow().contains(room) => {
+ self.action_set_enabled("invite.accept", false);
+ self.action_set_enabled("invite.reject", false);
+ priv_.reject_button.set_loading(true);
+ }
+ _ => self.reset(),
+ }
+
+ if let Some(category_handler) = priv_.category_handler.take() {
+ if let Some(room) = self.room() {
+ room.disconnect(category_handler);
+ }
+ }
+
+ // FIXME: remove clousure when room changes
+ if let Some(ref room) = room {
+ let handler_id = room.connect_notify_local(
+ Some("category"),
+ clone!(@weak self as obj => move |room, _| {
+ if room.category() != CategoryType::Invited {
+ let priv_ = imp::Invite::from_instance(&obj);
+ priv_.reject_requests.borrow_mut().remove(&room);
+ priv_.accept_requests.borrow_mut().remove(&room);
+ obj.reset();
+ if let Some(category_handler) = priv_.category_handler.take() {
+ room.disconnect(category_handler);
+ }
+ }
+ }),
+ );
+ priv_.category_handler.replace(Some(handler_id));
+ }
+
+ priv_.room.replace(room);
+
+ self.notify("room");
+ }
+
+ pub fn room(&self) -> Option<Room> {
+ let priv_ = imp::Invite::from_instance(self);
+ priv_.room.borrow().clone()
+ }
+
+ fn reset(&self) {
+ let priv_ = imp::Invite::from_instance(self);
+ priv_.accept_button.set_loading(false);
+ priv_.reject_button.set_loading(false);
+ self.action_set_enabled("invite.accept", true);
+ self.action_set_enabled("invite.reject", true);
+ }
+
+ fn accept(&self) -> Option<()> {
+ let priv_ = imp::Invite::from_instance(self);
+ let room = self.room()?;
+
+ self.action_set_enabled("invite.accept", false);
+ self.action_set_enabled("invite.reject", false);
+ priv_.accept_button.set_loading(true);
+ priv_.accept_requests.borrow_mut().insert(room.clone());
+
+ spawn!(
+ clone!(@weak self as obj, @strong room => move || async move {
+ let priv_ = imp::Invite::from_instance(&obj);
+ let result = room.accept_invite().await;
+ match result {
+ Ok(_) => {},
+ Err(error) => {
+ // FIXME: display an error to the user
+ error!("Accepting invitiation failed: {}", error);
+ priv_.accept_requests.borrow_mut().remove(&room);
+ obj.reset();
+ },
+ }
+ })()
+ );
+
+ Some(())
+ }
+
+ fn reject(&self) -> Option<()> {
+ let priv_ = imp::Invite::from_instance(self);
+ let room = self.room()?;
+
+ self.action_set_enabled("invite.accept", false);
+ self.action_set_enabled("invite.reject", false);
+ priv_.reject_button.set_loading(true);
+ priv_.reject_requests.borrow_mut().insert(room.clone());
+
+ spawn!(
+ clone!(@weak self as obj, @strong room => move || async move {
+ let priv_ = imp::Invite::from_instance(&obj);
+ let result = room.reject_invite().await;
+ match result {
+ Ok(_) => {},
+ Err(error) => {
+ // FIXME: display an error to the user
+ error!("Rejecting invitiation failed: {}", error);
+ priv_.reject_requests.borrow_mut().remove(&room);
+ obj.reset();
+ },
+ }
+ })()
+ );
+
+ Some(())
+ }
+}
diff --git a/src/session/content/mod.rs b/src/session/content/mod.rs
index 2aa007f0..3109a2ed 100644
--- a/src/session/content/mod.rs
+++ b/src/session/content/mod.rs
@@ -1,5 +1,6 @@
mod content;
mod divider_row;
+mod invite;
mod item_row;
mod markdown_popover;
mod message_row;
@@ -8,6 +9,7 @@ mod state_row;
pub use self::content::Content;
use self::divider_row::DividerRow;
+use self::invite::Invite;
use self::item_row::ItemRow;
use self::markdown_popover::MarkdownPopover;
use self::message_row::MessageRow;
diff --git a/src/session/mod.rs b/src/session/mod.rs
index fd4038e9..6cef602e 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -466,6 +466,25 @@ impl Session {
);
}
}
- // TODO: handle StrippedStateEvents for invited rooms
+
+ for (room_id, matrix_room) in response.rooms.invite {
+ if let Some(room) = rooms_map.get(&room_id) {
+ room.handle_invite_events(
+ matrix_room
+ .invite_state
+ .events
+ .into_iter()
+ .filter_map(|event| {
+ if let Ok(event) = event.deserialize() {
+ Some(event)
+ } else {
+ error!("Couldn't deserialize event: {:?}", event);
+ None
+ }
+ })
+ .collect(),
+ )
+ }
+ }
}
}
diff --git a/src/session/room/room.rs b/src/session/room/room.rs
index 0748cb5a..63e7f045 100644
--- a/src/session/room/room.rs
+++ b/src/session/room/room.rs
@@ -4,13 +4,14 @@ use log::{debug, error, warn};
use matrix_sdk::{
events::{
room::{
- member::MemberEventContent,
+ member::{MemberEventContent, MembershipState},
message::{
EmoteMessageEventContent, MessageEventContent, MessageType, TextMessageEventContent,
},
},
tag::TagName,
- AnyMessageEvent, AnyRoomEvent, AnyStateEvent, MessageEvent, StateEvent, Unsigned,
+ AnyMessageEvent, AnyRoomEvent, AnyStateEvent, AnyStrippedStateEvent, MessageEvent,
+ StateEvent, Unsigned,
},
identifiers::{EventId, RoomId, UserId},
room::Room as MatrixRoom,
@@ -25,6 +26,7 @@ use crate::session::{
User,
};
use crate::utils::do_async;
+use crate::RUNTIME;
mod imp {
use super::*;
@@ -43,6 +45,8 @@ mod imp {
pub room_members: RefCell<HashMap<UserId, User>>,
/// The user of this room
pub user_id: OnceCell<UserId>,
+ /// The user who send the invite to this room. This is only set when this room is an invitiation.
+ pub inviter: RefCell<Option<User>>,
}
#[glib::object_subclass]
@@ -77,6 +81,13 @@ mod imp {
User::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
),
+ glib::ParamSpec::new_object(
+ "inviter",
+ "Inviter",
+ "The user who send the invite to this room, this is only set when this room
rapresents an invite",
+ User::static_type(),
+ glib::ParamFlags::READABLE,
+ ),
glib::ParamSpec::new_object(
"avatar",
"Avatar",
@@ -154,6 +165,7 @@ mod imp {
let matrix_room = matrix_room.as_ref().unwrap();
match pspec.name() {
"user" => obj.user().to_value(),
+ "inviter" => obj.inviter().to_value(),
"display-name" => obj.display_name().to_value(),
"avatar" => self.avatar.borrow().to_value(),
"timeline" => self.timeline.get().unwrap().to_value(),
@@ -348,6 +360,11 @@ impl Room {
.filter(|topic| !topic.is_empty() && topic.find(|c: char| !c.is_whitespace()).is_some())
}
+ pub fn inviter(&self) -> Option<User> {
+ let priv_ = imp::Room::from_instance(&self);
+ priv_.inviter.borrow().clone()
+ }
+
/// Returns the room member `User` object
///
/// The returned `User` is specific to this room
@@ -361,6 +378,42 @@ impl Room {
.clone()
}
+ /// Handle stripped state events.
+ ///
+ /// Events passed to this function arn't added to the timeline.
+ pub fn handle_invite_events(&self, events: Vec<AnyStrippedStateEvent>) {
+ let priv_ = imp::Room::from_instance(self);
+ let invite_event = events
+ .iter()
+ .find(|event| {
+ if let AnyStrippedStateEvent::RoomMember(event) = event {
+ event.content.membership == MembershipState::Invite
+ && event.state_key == self.user().user_id().as_str()
+ } else {
+ false
+ }
+ })
+ .unwrap();
+
+ let inviter_id = invite_event.sender();
+
+ let inviter_event = events.iter().find(|event| {
+ if let AnyStrippedStateEvent::RoomMember(event) = event {
+ &event.sender == inviter_id
+ } else {
+ false
+ }
+ });
+
+ let inviter = User::new(inviter_id);
+ if let Some(AnyStrippedStateEvent::RoomMember(event)) = inviter_event {
+ inviter.update_from_stripped_member_event(event);
+ }
+
+ priv_.inviter.replace(Some(inviter));
+ self.notify("inviter");
+ }
+
/// Add new events to the timeline
pub fn append_events(&self, batch: Vec<AnyRoomEvent>) {
let priv_ = imp::Room::from_instance(self);
@@ -507,4 +560,30 @@ impl Room {
);
}
}
+
+ pub async fn accept_invite(&self) -> matrix_sdk::Result<()> {
+ let matrix_room = self.matrix_room();
+
+ if let MatrixRoom::Invited(matrix_room) = matrix_room {
+ let (sender, receiver) = futures::channel::oneshot::channel();
+ RUNTIME.spawn(async move { sender.send(matrix_room.accept_invitation().await) });
+ receiver.await.unwrap()
+ } else {
+ error!("Can't accept invite, because this room isn't an invited room");
+ Ok(())
+ }
+ }
+
+ pub async fn reject_invite(&self) -> matrix_sdk::Result<()> {
+ let matrix_room = self.matrix_room();
+
+ if let MatrixRoom::Invited(matrix_room) = matrix_room {
+ let (sender, receiver) = futures::channel::oneshot::channel();
+ RUNTIME.spawn(async move { sender.send(matrix_room.reject_invitation().await) });
+ receiver.await.unwrap()
+ } else {
+ error!("Can't reject invite, because this room isn't an invited room");
+ Ok(())
+ }
+ }
}
diff --git a/src/session/user.rs b/src/session/user.rs
index 41fa12db..d31b8592 100644
--- a/src/session/user.rs
+++ b/src/session/user.rs
@@ -1,7 +1,7 @@
use gtk::{gio, glib, prelude::*, subclass::prelude::*};
use matrix_sdk::{
- events::{room::member::MemberEventContent, StateEvent},
+ events::{room::member::MemberEventContent, StateEvent, StrippedStateEvent},
identifiers::UserId,
RoomMember,
};
@@ -174,4 +174,41 @@ impl User {
self.notify("display-name");
}
}
+
+ /// Update the user based on the the stripped room member state event
+ //TODO: create the GLoadableIcon and set `avatar`
+ pub fn update_from_stripped_member_event(
+ &self,
+ event: &StrippedStateEvent<MemberEventContent>,
+ ) {
+ let changed = {
+ let priv_ = imp::User::from_instance(&self);
+ let user_id = priv_.user_id.get().unwrap();
+ if event.sender.as_str() != user_id {
+ return;
+ };
+
+ let display_name = if let Some(display_name) = &event.content.displayname {
+ Some(display_name.to_owned())
+ } else {
+ event
+ .content
+ .third_party_invite
+ .as_ref()
+ .map(|i| i.display_name.to_owned())
+ };
+
+ let mut current_display_name = priv_.display_name.borrow_mut();
+ if *current_display_name != display_name {
+ *current_display_name = display_name;
+ true
+ } else {
+ false
+ }
+ };
+
+ if changed {
+ self.notify("display-name");
+ }
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]