Invite dialog working
With this patch you'll be able to invite other users to one room
This commit is contained in:
parent
bb43d1f486
commit
6472b1f486
8 changed files with 403 additions and 4 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -63,3 +63,8 @@
|
|||
padding: 10px;
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
.member-uid {
|
||||
color: @insensitive_fg_color;
|
||||
font-size: x-small;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)) => {
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue