diff --git a/TODO b/TODO index 36c38d5e..923fc31e 100644 --- a/TODO +++ b/TODO @@ -7,7 +7,11 @@ Fixs: * Ignore launched threads when changing room... * Sort rooms by last message or fav? + * Load more should work with search, currently loads the room messages, + but not continues with the search + Functionality: + * Show event messages in message list * Register * Room creation diff --git a/res/main_window.glade b/res/main_window.glade index 8f38d1b7..d822ac3a 100644 --- a/res/main_window.glade +++ b/res/main_window.glade @@ -177,7 +177,7 @@ - + True True center @@ -776,6 +776,20 @@ Join a room to start to chat False end + + + gtk-cancel + True + True + True + True + + + True + True + 0 + + gtk-apply @@ -793,20 +807,6 @@ Join a room to start to chat 1 - - - gtk-cancel - True - True - True - True - - - True - True - 0 - - False @@ -965,6 +965,75 @@ Join a room to start to chat + + False + search_button + bottom + + + True + False + 5 + 5 + 5 + 5 + 3 + + + True + True + edit-find-symbolic + False + False + + + True + True + 0 + + + + + True + False + + + gtk-find + True + True + True + True + + + + normal + normal + + + + + True + False + True + + + searching + Searching + 1 + + + + + False + True + 2 + + + + + False user_button diff --git a/src/app.rs b/src/app.rs index 6a25a0d2..fd3db07f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -388,17 +388,20 @@ impl AppOp { self.backend.send(BKCommand::SyncForced).unwrap(); } - pub fn set_active_room(&mut self, room: String, name: String) { - self.active_room = room; - - self.room_panel(RoomPanel::Loading); - + pub fn remove_messages(&mut self) { let messages = self.gtk_builder .get_object::("message_list") .expect("Can't find message_list in ui file."); for ch in messages.get_children().iter().skip(1) { messages.remove(ch); } + } + + pub fn set_active_room(&mut self, room: String, name: String) { + self.active_room = room; + + self.room_panel(RoomPanel::Loading); + self.remove_messages(); self.members.clear(); let members = self.gtk_builder @@ -965,6 +968,24 @@ impl AppOp { None => {} } } + + pub fn search(&mut self, term: Option) { + let r = self.active_room.clone(); + self.remove_messages(); + self.backend.send(BKCommand::Search(r, term)).unwrap(); + + self.gtk_builder + .get_object::("search_button_stack") + .expect("Can't find search_button_stack in ui file.") + .set_visible_child_name("searching"); + } + + pub fn search_end(&self) { + self.gtk_builder + .get_object::("search_button_stack") + .expect("Can't find search_button_stack in ui file.") + .set_visible_child_name("normal"); + } } /// State for the main thread. @@ -1108,6 +1129,9 @@ impl App { Ok(BKResponse::AttachedFile(msg)) => { theop.lock().unwrap().add_tmp_room_message(&msg); } + Ok(BKResponse::SearchEnd) => { + theop.lock().unwrap().search_end(); + } // errors Ok(BKResponse::SyncError(_)) => { @@ -1170,6 +1194,8 @@ impl App { self.connect_directory(); self.connect_room_config(); + + self.connect_search(); } fn connect_room_config(&self) { @@ -1328,6 +1354,33 @@ impl App { user_button.connect_clicked(move |_| user_menu.show_all()); } + fn connect_search(&self) { + // Set up search popover + let search_button: gtk::Button = self.gtk_builder + .get_object("search_button") + .expect("Couldn't find search_button in ui file."); + + let search_popover: gtk::Popover = self.gtk_builder + .get_object("search_popover") + .expect("Couldn't find search_popover in ui file."); + + let input: gtk::Entry = self.gtk_builder + .get_object("search_input") + .expect("Couldn't find search_input in ui file."); + + let btn: gtk::Button = self.gtk_builder + .get_object("search") + .expect("Couldn't find search in ui file."); + + search_button.connect_clicked(move |_| search_popover.show_all()); + + let op = self.op.clone(); + input.connect_activate(move |inp| op.lock().unwrap().search(inp.get_text())); + let op = self.op.clone(); + btn.connect_clicked(move |_| op.lock().unwrap().search(input.get_text())); + + } + fn connect_login_button(&self) { // Login click let login_btn: gtk::Button = self.gtk_builder diff --git a/src/backend.rs b/src/backend.rs index 322baf8b..229f8653 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -72,6 +72,7 @@ pub enum BKCommand { SetRoomTopic(String, String), SetRoomAvatar(String, String), AttachFile(String, String), + Search(String, Option), } #[derive(Debug)] @@ -102,6 +103,7 @@ pub enum BKResponse { RoomTopic(String, String), Media(String), AttachedFile(Message), + SearchEnd, //errors UserNameError(Error), @@ -126,6 +128,7 @@ pub enum BKResponse { GetRoomAvatarError(Error), MediaError(Error), AttachFileError(Error), + SearchError(Error), } @@ -272,6 +275,10 @@ impl Backend { let r = self.attach_file(roomid, fname); bkerror!(r, tx, BKResponse::AttachFileError); } + Ok(BKCommand::Search(roomid, term)) => { + let r = self.search(roomid, term); + bkerror!(r, tx, BKResponse::SearchError); + } Ok(BKCommand::ShutDown) => { return false; } @@ -913,7 +920,7 @@ impl Backend { Ok(js) => { let uri = js["content_uri"].as_str().unwrap_or(""); let attrs = json!({ "url": uri }); - match json_q("put", &roomurl, &attrs) { + match json_q("put", &roomurl, &attrs, 10) { Ok(_) => { tx.send(BKResponse::SetRoomAvatar).unwrap(); }, @@ -985,4 +992,65 @@ impl Backend { Ok(()) } + + pub fn search(&self, roomid: String, term: Option) -> Result<(), Error> { + let tx = self.tx.clone(); + + match term { + Some(ref t) if !t.is_empty() => { + self.make_search(roomid, t.clone()) + } + _ => { + tx.send(BKResponse::SearchEnd).unwrap(); + self.get_room_messages(roomid, false) + } + } + } + + pub fn make_search(&self, roomid: String, term: String) -> Result<(), Error> { + let url = self.url("search", vec![])?; + + let attrs = json!({ + "search_categories": { + "room_events": { + "keys": ["content.body"], + "search_term": term, + "filter": { + "rooms": [ roomid.clone() ], + }, + "order_by": "recent", + }, + }, + }); + + let tx = self.tx.clone(); + let baseu = self.get_base_url()?; + + thread::spawn(move || { + match json_q("post", &url, &attrs, 0) { + Ok(js) => { + tx.send(BKResponse::SearchEnd).unwrap(); + let mut ms: Vec = vec![]; + + let res = &js["search_categories"]["room_events"]["results"]; + for search in res.as_array().unwrap().iter().rev() { + let msg = &search["result"]; + if msg["type"].as_str().unwrap_or("") != "m.room.message" { + continue; + } + + let m = parse_room_message(&baseu, roomid.clone(), msg); + ms.push(m); + } + tx.send(BKResponse::RoomMessagesInit(ms)).unwrap(); + } + Err(err) => { + tx.send(BKResponse::SearchEnd).unwrap(); + tx.send(BKResponse::SearchError(err)).unwrap() + } + }; + }); + + Ok(()) + } } diff --git a/src/util.rs b/src/util.rs index 2cd3c7f6..cbc6fc42 100644 --- a/src/util.rs +++ b/src/util.rs @@ -120,7 +120,7 @@ macro_rules! post { macro_rules! query { ($method: expr, $url: expr, $attrs: expr, $okcb: expr, $errcb: expr) => { thread::spawn(move || { - let js = json_q($method, $url, $attrs); + let js = json_q($method, $url, $attrs, 10); match js { Ok(r) => { @@ -338,10 +338,12 @@ pub fn age_to_datetime(age: i64) -> DateTime { now - diff } -pub fn json_q(method: &str, url: &Url, attrs: &JsonValue) -> Result { - let client = reqwest::ClientBuilder::new()? - .timeout(StdDuration::from_secs(10)) - .build()?; +pub fn json_q(method: &str, url: &Url, attrs: &JsonValue, timeout: u64) -> Result { + let mut clientb = reqwest::ClientBuilder::new()?; + let client = match timeout { + 0 => clientb.build()?, + n => clientb.timeout(StdDuration::from_secs(n)).build()? + }; let mut conn = match method { "post" => client.post(url.as_str())?, @@ -367,7 +369,7 @@ pub fn get_user_avatar(baseu: &Url, userid: &str) -> Result<(String, String), Er let url = client_url!(baseu, &format!("profile/{}", userid), vec![])?; let attrs = json!(null); - match json_q("get", &url, &attrs) { + match json_q("get", &url, &attrs, 10) { Ok(js) => { let name = String::from(js["displayname"].as_str().unwrap_or("@")); match js["avatar_url"].as_str() { @@ -383,7 +385,7 @@ pub fn get_room_st(base: &Url, tk: &str, roomid: &str) -> Result