From d8713141f26dbb28f504ad46ea94a2b8bd63de14 Mon Sep 17 00:00:00 2001 From: Ulyssa Date: Thu, 26 Jan 2023 15:23:15 -0800 Subject: [PATCH] Display room tags in list of direct messages (#21) --- src/windows/mod.rs | 150 +++++++++++++++++++++++++++++---------------- src/worker.rs | 41 +++++-------- 2 files changed, 115 insertions(+), 76 deletions(-) diff --git a/src/windows/mod.rs b/src/windows/mod.rs index b2f78f5..2413c72 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -124,6 +124,57 @@ fn room_cmp(a: &MatrixRoom, b: &MatrixRoom) -> Ordering { ord.then_with(|| a.room_id().cmp(b.room_id())) } +fn tag_cmp(a: &Option, b: &Option) -> Ordering { + let (fava, lowa) = a + .as_ref() + .map(|tags| { + (tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority)) + }) + .unwrap_or((false, false)); + + let (favb, lowb) = b + .as_ref() + .map(|tags| { + (tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority)) + }) + .unwrap_or((false, false)); + + // If a has Favorite and b doesn't, it should sort earlier in room list. + let cmpf = favb.cmp(&fava); + + // If a has LowPriority and b doesn't, it should sort later in room list. + let cmpl = lowa.cmp(&lowb); + + cmpl.then(cmpf) +} + +fn append_tags<'a>(tags: &'a Tags, spans: &mut Vec>, style: Style) { + if tags.is_empty() { + return; + } + + spans.push(Span::styled(" (", style)); + + for (i, tag) in tags.keys().enumerate() { + if i > 0 { + spans.push(Span::styled(", ", style)); + } + + match tag { + TagName::Favorite => spans.push(Span::styled("Favorite", style)), + TagName::LowPriority => spans.push(Span::styled("Low Priority", style)), + TagName::ServerNotice => spans.push(Span::styled("Server Notice", style)), + TagName::User(tag) => { + spans.push(Span::styled("User Tag: ", style)); + spans.push(Span::styled(tag.as_ref(), style)); + }, + tag => spans.push(Span::styled(format!("{:?}", tag), style)), + } + } + + spans.push(Span::styled(")", style)); +} + #[inline] fn room_prompt( room_id: &RoomId, @@ -326,8 +377,13 @@ impl WindowOps for IambWindow { IambWindow::Room(state) => state.draw(area, buf, focused, store), IambWindow::DirectList(state) => { let dms = store.application.worker.direct_messages(); - let items = dms.into_iter().map(|(id, name)| DirectItem::new(id, name, store)); - state.set(items.collect()); + let mut items = dms + .into_iter() + .map(|(id, name, tags)| DirectItem::new(id, name, tags, store)) + .collect::>(); + items.sort(); + + state.set(items); List::new(store) .empty_message("No direct messages yet!") @@ -583,29 +639,7 @@ impl Eq for RoomItem {} impl Ord for RoomItem { fn cmp(&self, other: &Self) -> Ordering { - let (fava, lowa) = self - .tags - .as_ref() - .map(|tags| { - (tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority)) - }) - .unwrap_or((false, false)); - - let (favb, lowb) = other - .tags - .as_ref() - .map(|tags| { - (tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority)) - }) - .unwrap_or((false, false)); - - // If self has Favorite and other doesn't, it should sort earlier in room list. - let cmpf = favb.cmp(&fava); - - // If self has LowPriority and other doesn't, it should sort later in room list. - let cmpl = lowa.cmp(&lowb); - - cmpl.then(cmpf).then_with(|| room_cmp(&self.room, &other.room)) + tag_cmp(&self.tags, &other.tags).then_with(|| room_cmp(&self.room, &other.room)) } } @@ -627,30 +661,7 @@ impl ListItem for RoomItem { let style = selected_style(selected); let mut spans = vec![Span::styled(self.name.as_str(), style)]; - if tags.is_empty() { - return Text::from(Spans(spans)); - } - - spans.push(Span::styled(" (", style)); - - for (i, tag) in tags.keys().enumerate() { - if i > 0 { - spans.push(Span::styled(", ", style)); - } - - match tag { - TagName::Favorite => spans.push(Span::styled("Favorite", style)), - TagName::LowPriority => spans.push(Span::styled("Low Priority", style)), - TagName::ServerNotice => spans.push(Span::styled("Server Notice", style)), - TagName::User(tag) => { - spans.push(Span::styled("User Tag: ", style)); - spans.push(Span::styled(tag.as_ref(), style)); - }, - tag => spans.push(Span::styled(format!("{:?}", tag), style)), - } - } - - spans.push(Span::styled(")", style)); + append_tags(tags, &mut spans, style); Text::from(Spans(spans)) } else { @@ -677,16 +688,22 @@ impl Promptable for RoomItem { #[derive(Clone)] pub struct DirectItem { room: MatrixRoom, + tags: Option, name: String, } impl DirectItem { - fn new(room: MatrixRoom, name: DisplayName, store: &mut ProgramStore) -> Self { + fn new( + room: MatrixRoom, + name: DisplayName, + tags: Option, + store: &mut ProgramStore, + ) -> Self { let name = name.to_string(); store.application.set_room_name(room.room_id(), name.as_str()); - DirectItem { room, name } + DirectItem { room, tags, name } } } @@ -698,7 +715,16 @@ impl ToString for DirectItem { impl ListItem for DirectItem { fn show(&self, selected: bool, _: &ViewportContext, _: &mut ProgramStore) -> Text { - selected_text(self.name.as_str(), selected) + if let Some(tags) = &self.tags { + let style = selected_style(selected); + let mut spans = vec![Span::styled(self.name.as_str(), style)]; + + append_tags(tags, &mut spans, style); + + Text::from(Spans(spans)) + } else { + selected_text(self.name.as_str(), selected) + } } fn get_word(&self) -> Option { @@ -706,6 +732,26 @@ impl ListItem for DirectItem { } } +impl PartialEq for DirectItem { + fn eq(&self, other: &Self) -> bool { + self.room.room_id() == other.room.room_id() + } +} + +impl Eq for DirectItem {} + +impl Ord for DirectItem { + fn cmp(&self, other: &Self) -> Ordering { + tag_cmp(&self.tags, &other.tags).then_with(|| room_cmp(&self.room, &other.room)) + } +} + +impl PartialOrd for DirectItem { + fn partial_cmp(&self, other: &Self) -> Option { + self.cmp(other).into() + } +} + impl Promptable for DirectItem { fn prompt( &mut self, diff --git a/src/worker.rs b/src/worker.rs index 1fc0931..a98399c 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -99,14 +99,16 @@ fn oneshot() -> (ClientReply, ClientResponse) { return (reply, response); } +pub type FetchedRoom = (MatrixRoom, DisplayName, Option); + pub enum WorkerTask { - ActiveRooms(ClientReply)>>), - DirectMessages(ClientReply>), + ActiveRooms(ClientReply>), + DirectMessages(ClientReply>), Init(AsyncProgramStore, ClientReply<()>), LoadOlder(OwnedRoomId, Option, u32, ClientReply), Login(LoginStyle, ClientReply>), GetInviter(Invited, ClientReply>>), - GetRoom(OwnedRoomId, ClientReply)>>), + GetRoom(OwnedRoomId, ClientReply>), JoinRoom(String, ClientReply>), Members(OwnedRoomId, ClientReply>>), SpaceMembers(OwnedRoomId, ClientReply>>), @@ -235,7 +237,7 @@ impl Requester { return response.recv(); } - pub fn direct_messages(&self) -> Vec<(MatrixRoom, DisplayName)> { + pub fn direct_messages(&self) -> Vec { let (reply, response) = oneshot(); self.tx.send(WorkerTask::DirectMessages(reply)).unwrap(); @@ -251,10 +253,7 @@ impl Requester { return response.recv(); } - pub fn get_room( - &self, - room_id: OwnedRoomId, - ) -> IambResult<(MatrixRoom, DisplayName, Option)> { + pub fn get_room(&self, room_id: OwnedRoomId) -> IambResult { let (reply, response) = oneshot(); self.tx.send(WorkerTask::GetRoom(room_id, reply)).unwrap(); @@ -270,7 +269,7 @@ impl Requester { return response.recv(); } - pub fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName, Option)> { + pub fn active_rooms(&self) -> Vec { let (reply, response) = oneshot(); self.tx.send(WorkerTask::ActiveRooms(reply)).unwrap(); @@ -704,14 +703,9 @@ impl ClientWorker { Ok(Some(InfoMessage::from("Successfully logged in!"))) } - async fn direct_message( - &mut self, - user: OwnedUserId, - ) -> IambResult<(MatrixRoom, DisplayName, Option)> { - for (room, name) in self.direct_messages().await { + async fn direct_message(&mut self, user: OwnedUserId) -> IambResult { + for (room, name, tags) in self.direct_messages().await { if room.get_member(user.as_ref()).await.map_err(IambError::from)?.is_some() { - let tags = room.tags().await.map_err(IambError::from)?; - return Ok((room, name, tags)); } } @@ -746,10 +740,7 @@ impl ClientWorker { Ok(details.inviter) } - async fn get_room( - &mut self, - room_id: OwnedRoomId, - ) -> IambResult<(MatrixRoom, DisplayName, Option)> { + async fn get_room(&mut self, room_id: OwnedRoomId) -> IambResult { if let Some(room) = self.client.get_room(&room_id) { let name = room.display_name().await.map_err(IambError::from)?; let tags = room.tags().await.map_err(IambError::from)?; @@ -783,7 +774,7 @@ impl ClientWorker { } } - async fn direct_messages(&self) -> Vec<(MatrixRoom, DisplayName)> { + async fn direct_messages(&self) -> Vec { let mut rooms = vec![]; for room in self.client.invited_rooms().into_iter() { @@ -792,8 +783,9 @@ impl ClientWorker { } let name = room.display_name().await.unwrap_or(DisplayName::Empty); + let tags = room.tags().await.unwrap_or_default(); - rooms.push((room.into(), name)); + rooms.push((room.into(), name, tags)); } for room in self.client.joined_rooms().into_iter() { @@ -802,14 +794,15 @@ impl ClientWorker { } let name = room.display_name().await.unwrap_or(DisplayName::Empty); + let tags = room.tags().await.unwrap_or_default(); - rooms.push((room.into(), name)); + rooms.push((room.into(), name, tags)); } return rooms; } - async fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName, Option)> { + async fn active_rooms(&self) -> Vec { let mut rooms = vec![]; for room in self.client.invited_rooms().into_iter() {