Invite dialog working

With this patch you'll be able to invite other users to one room
This commit is contained in:
Daniel García Moreno 2018-01-19 12:16:38 +01:00
parent bb43d1f486
commit 6472b1f486
8 changed files with 403 additions and 4 deletions

View file

@ -120,6 +120,10 @@ impl Backend {
let r = user::get_user_info_async(self, &sender, ctx);
bkerror!(r, tx, BKResponse::CommandError);
}
Ok(BKCommand::UserSearch(term)) => {
let r = user::search(self, term);
bkerror!(r, tx, BKResponse::CommandError);
}
// Sync module
@ -206,6 +210,10 @@ impl Backend {
let r = room::leave_room(self, roomid);
bkerror!(r, tx, BKResponse::RejectInvError);
}
Ok(BKCommand::Invite(room, userid)) => {
let r = room::invite(self, room, userid);
bkerror!(r, tx, BKResponse::InviteError);
}
// Media module

View file

@ -525,3 +525,19 @@ pub fn add_to_fav(bk: &Backend, roomid: String, tofav: bool) -> Result<(), Error
Ok(())
}
pub fn invite(bk: &Backend, roomid: String, userid: String) -> Result<(), Error> {
let url = bk.url(&format!("rooms/{}/invite", roomid), vec![])?;
let attrs = json!({
"user_id": userid,
});
let tx = bk.tx.clone();
post!(&url, &attrs,
|_| { },
|err| { tx.send(BKResponse::InviteError(err)).unwrap(); }
);
Ok(())
}

View file

@ -49,6 +49,8 @@ pub enum BKCommand {
AddToFav(String, bool),
AcceptInv(String),
RejectInv(String),
UserSearch(String),
Invite(String, String),
}
#[derive(Debug)]
@ -85,6 +87,7 @@ pub enum BKResponse {
NewRoom(Room),
AddedToFav(String, bool),
RoomNotifications(String, i32, i32),
UserSearch(Vec<Member>),
//errors
UserNameError(Error),
@ -115,6 +118,7 @@ pub enum BKResponse {
AddToFavError(Error),
AcceptInvError(Error),
RejectInvError(Error),
InviteError(Error),
}
#[derive(Debug)]

View file

@ -143,3 +143,41 @@ pub fn get_avatar_async(bk: &Backend, member: Option<Member>, tx: Sender<String>
Ok(())
}
pub fn search(bk: &Backend, term: String) -> Result<(), Error> {
let url = bk.url(&format!("user_directory/search"), vec![])?;
let attrs = json!({
"search_term": term,
});
let tx = bk.tx.clone();
post!(&url, &attrs,
|js: JsonValue| {
let mut users: Vec<Member> = vec![];
if let Some(arr) = js["results"].as_array() {
for member in arr.iter() {
let alias = match member["display_name"].as_str() {
None => None,
Some(a) => Some(a.to_string()),
};
let avatar = match member["avatar_url"].as_str() {
None => None,
Some(a) => Some(a.to_string()),
};
users.push(Member{
alias: alias,
uid: member["user_id"].as_str().unwrap_or_default().to_string(),
avatar: avatar,
});
}
}
tx.send(BKResponse::UserSearch(users)).unwrap();
},
|err| {
tx.send(BKResponse::CommandError(err)).unwrap(); }
);
Ok(())
}

View file

@ -63,3 +63,8 @@
padding: 10px;
font-size: x-small;
}
.member-uid {
color: @insensitive_fg_color;
font-size: x-small;
}

View file

@ -2,6 +2,30 @@
<!-- Generated with glade 3.20.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkPopover" id="autocomplete_popover">
<property name="width_request">300</property>
<property name="height_request">200</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkListBox" id="autocomplete_listbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="file_chooser_preview">
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -137,6 +161,20 @@
<property name="margin_bottom">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkModelButton" id="room_invite">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="action_name">app.room_invite</property>
<property name="text" translatable="yes">Invite to this room</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="room_menu">
<property name="visible">True</property>
@ -1370,6 +1408,138 @@ Join a room to start to chat</property>
<class name="leave-room"/>
</style>
</object>
<object class="GtkDialog" id="invite_user_dialog">
<property name="width_request">400</property>
<property name="height_request">300</property>
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="window_position">center</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="gravity">center</property>
<property name="transient_for">main_window</property>
<property name="attached_to">main_window</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="invite_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="placeholder_text" translatable="yes">Matrix username, email or phone number</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkListBox" id="to_invite">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child type="title">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkButton" id="cancel_invite">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Invite</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="invite_button">
<property name="label" translatable="yes">Invite</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkMessageDialog" id="leave_room_dialog">
<property name="can_focus">False</property>
<property name="resizable">False</property>

View file

@ -680,7 +680,7 @@ impl AppOp {
}
}
pub fn show_inv_dialog(&self, r: &Room) {
pub fn show_inv_dialog(&mut self, r: &Room) {
let dialog = self.gtk_builder
.get_object::<gtk::MessageDialog>("invite_dialog")
.expect("Can't find invite_dialog in ui file.");
@ -720,6 +720,93 @@ impl AppOp {
self.roomlist.remove_room(roomid);
}
pub fn search_invite_user(&self, term: Option<String>) {
if let Some(t) = term {
self.backend.send(BKCommand::UserSearch(t)).unwrap();
}
}
pub fn search_invite_finished(&self, users: Vec<Member>) {
let listbox = self.gtk_builder
.get_object::<gtk::ListBox>("autocomplete_listbox")
.expect("Can't find autocomplete_listbox in ui file.");
let popover = self.gtk_builder
.get_object::<gtk::Popover>("autocomplete_popover")
.expect("Can't find autocomplete_popover in ui file.");
let entry = self.gtk_builder
.get_object::<gtk::Entry>("invite_entry")
.expect("Can't find invite_entry in ui file.");
let to_invite = self.gtk_builder
.get_object::<gtk::ListBox>("to_invite")
.expect("Can't find to_invite in ui file.");
for ch in listbox.get_children().iter() {
listbox.remove(ch);
}
for (i, u) in users.iter().enumerate() {
let w;
let w1;
{
let mb = widgets::MemberBox::new(u, &self);
w = mb.widget(true);
w1 = mb.widget(true);
}
w.connect_button_press_event(clone!(to_invite, entry, u, w1, popover => move |_, _| {
entry.set_text(&u.uid);
for ch in to_invite.get_children().iter() {
to_invite.remove(ch);
}
to_invite.insert(&w1, 0);
popover.popdown();
glib::signal::Inhibit(true)
}));
listbox.insert(&w, i as i32);
}
popover.set_relative_to(Some(&entry));
popover.set_modal(false);
popover.popup();
}
pub fn close_invite_dialog(&self) {
let listbox = self.gtk_builder
.get_object::<gtk::ListBox>("autocomplete_listbox")
.expect("Can't find autocomplete_listbox in ui file.");
let to_invite = self.gtk_builder
.get_object::<gtk::ListBox>("to_invite")
.expect("Can't find to_invite in ui file.");
let entry = self.gtk_builder
.get_object::<gtk::Entry>("invite_entry")
.expect("Can't find invite_entry in ui file.");
let dialog = self.gtk_builder
.get_object::<gtk::Dialog>("invite_user_dialog")
.expect("Can't find invite_user_dialog in ui file.");
for ch in to_invite.get_children().iter() {
to_invite.remove(ch);
}
for ch in listbox.get_children().iter() {
listbox.remove(ch);
}
entry.set_text("");
dialog.hide();
}
pub fn invite(&self) {
let entry = self.gtk_builder
.get_object::<gtk::Entry>("invite_entry")
.expect("Can't find invite_entry in ui file.");
if let (Some(ref t), &Some(ref r)) = (entry.get_text(), &self.active_room) {
self.backend.send(BKCommand::Invite(r.clone(), t.to_string())).unwrap();
}
self.close_invite_dialog();
}
pub fn set_active_room(&mut self, room: &Room) {
self.member_limit = 50;
self.room_panel(RoomPanel::Loading);
@ -1345,6 +1432,13 @@ impl AppOp {
dialog.present();
}
pub fn show_invite_user_dialog(&self) {
let dialog = self.gtk_builder
.get_object::<gtk::Dialog>("invite_user_dialog")
.expect("Can't find invite_user_dialog in ui file.");
dialog.present();
}
pub fn really_leave_active_room(&mut self) {
let r = self.active_room.clone().unwrap_or_default();
self.backend.send(BKCommand::LeaveRoom(r.clone())).unwrap();
@ -1734,7 +1828,7 @@ impl AppOp {
{
let mb = widgets::MemberBox::new(&m, &self);
w = mb.widget();
w = mb.widget(false);
}
let msg = msg_entry.clone();
@ -1888,6 +1982,7 @@ impl App {
self.connect_member_search();
self.connect_invite_dialog();
self.connect_invite_user();
}
fn create_actions(&self) {
@ -1898,6 +1993,7 @@ impl App {
let logout = gio::SimpleAction::new("logout", None);
let room = gio::SimpleAction::new("room_details", None);
let inv = gio::SimpleAction::new("room_invite", None);
let search = gio::SimpleAction::new("search", None);
let leave = gio::SimpleAction::new("leave_room", None);
@ -1908,6 +2004,7 @@ impl App {
self.op.lock().unwrap().gtk_app.add_action(&logout);
self.op.lock().unwrap().gtk_app.add_action(&room);
self.op.lock().unwrap().gtk_app.add_action(&inv);
self.op.lock().unwrap().gtk_app.add_action(&search);
self.op.lock().unwrap().gtk_app.add_action(&leave);
@ -1924,6 +2021,8 @@ impl App {
let op = self.op.clone();
room.connect_activate(move |_, _| { op.lock().unwrap().show_room_dialog(); });
let op = self.op.clone();
inv.connect_activate(move |_, _| { op.lock().unwrap().show_invite_user_dialog(); });
let op = self.op.clone();
search.connect_activate(move |_, _| { op.lock().unwrap().toggle_search(); });
let op = self.op.clone();
leave.connect_activate(move |_, _| { op.lock().unwrap().leave_active_room(); });
@ -2278,6 +2377,48 @@ impl App {
}));
}
fn connect_invite_user(&self) {
let op = &self.op;
let cancel = self.gtk_builder
.get_object::<gtk::Button>("cancel_invite")
.expect("Can't find cancel_invite in ui file.");
let invite = self.gtk_builder
.get_object::<gtk::Button>("invite_button")
.expect("Can't find invite_button in ui file.");
let entry = self.gtk_builder
.get_object::<gtk::Entry>("invite_entry")
.expect("Can't find invite_entry in ui file.");
// this is used to cancel the timeout and not search for every key input. We'll wait 500ms
// without key release event to launch the search
let source_id: Arc<Mutex<Option<glib::source::SourceId>>> = Arc::new(Mutex::new(None));
entry.connect_key_release_event(clone!(op => move |entry, _| {
{
let mut id = source_id.lock().unwrap();
if let Some(sid) = id.take() {
glib::source::source_remove(sid);
}
}
let sid = gtk::timeout_add(500, clone!(op, entry, source_id => move || {
op.lock().unwrap().search_invite_user(entry.get_text());
*(source_id.lock().unwrap()) = None;
gtk::Continue(false)
}));
*(source_id.lock().unwrap()) = Some(sid);
glib::signal::Inhibit(false)
}));
cancel.connect_clicked(clone!(op => move |_| {
op.lock().unwrap().close_invite_dialog();
}));
invite.connect_clicked(clone!(op => move |_| {
op.lock().unwrap().invite();
}));
}
pub fn run(&self) {
self.op.lock().unwrap().init();
@ -2400,6 +2541,9 @@ fn backend_loop(rx: Receiver<BKResponse>) {
Ok(BKResponse::AddedToFav(r, tofav)) => {
APPOP!(added_to_fav, (r, tofav));
}
Ok(BKResponse::UserSearch(users)) => {
APPOP!(search_invite_finished, (users));
}
// errors
Ok(BKResponse::NewRoomError(err)) => {

View file

@ -33,16 +33,25 @@ impl<'a> MemberBox<'a> {
}
}
pub fn widget(&self) -> gtk::EventBox {
pub fn widget(&self, show_uid: bool) -> gtk::EventBox {
let backend = self.op.backend.clone();
let username = gtk::Label::new("");
let uid = gtk::Label::new("");
let event_box = gtk::EventBox::new();
let w = gtk::Box::new(gtk::Orientation::Horizontal, 5);
let v = gtk::Box::new(gtk::Orientation::Vertical, 0);
uid.set_text(&self.member.uid);
uid.set_alignment(0.0, 0.0);
if let Some(style) = uid.get_style_context() {
style.add_class("member-uid");
}
username.set_text(&self.member.get_alias().unwrap_or_default());
username.set_tooltip_text(&self.member.get_alias().unwrap_or_default()[..]);
username.set_margin_end(5);
username.set_ellipsize(pango::EllipsizeMode::End);
username.set_alignment(0.0, 0.5);
if let Some(style) = username.get_style_context() {
style.add_class("member");
}
@ -53,8 +62,13 @@ impl<'a> MemberBox<'a> {
get_member_info(backend.clone(), avatar.clone(), username.clone(), self.member.uid.clone(), globals::USERLIST_ICON_SIZE, 10);
avatar.set_margin_start(5);
v.pack_start(&username, true, true, 0);
if show_uid {
v.pack_start(&uid, true, true, 0);
}
w.add(&avatar);
w.add(&username);
w.add(&v);
event_box.add(&w);
event_box.show_all();