multiverse/widgets/
room_list.rs1use std::{collections::HashMap, sync::Arc};
2
3use imbl::Vector;
4use matrix_sdk::{Client, Room, locks::Mutex, ruma::OwnedRoomId};
5use matrix_sdk_ui::sync_service::SyncService;
6use ratatui::{prelude::*, widgets::*};
7
8use crate::{
9 ALT_ROW_COLOR, HEADER_BG, NORMAL_ROW_COLOR, SELECTED_STYLE_FG, TEXT_COLOR,
10 widgets::status::StatusHandle,
11};
12
13#[derive(Clone)]
15pub struct ExtraRoomInfo {
16 pub raw_name: Option<String>,
18
19 pub display_name: Option<String>,
21
22 pub is_dm: Option<bool>,
24}
25
26pub type Rooms = Arc<Mutex<Vector<Room>>>;
27pub type RoomInfos = Arc<Mutex<HashMap<OwnedRoomId, ExtraRoomInfo>>>;
28
29pub struct RoomList {
30 pub state: ListState,
31
32 pub status_handle: StatusHandle,
33
34 pub rooms: Rooms,
35
36 client: Client,
37
38 room_infos: RoomInfos,
40
41 current_room_subscription: Option<Room>,
43
44 sync_service: Arc<SyncService>,
46}
47
48impl RoomList {
49 pub fn new(
50 client: Client,
51 rooms: Rooms,
52
53 room_infos: RoomInfos,
54 sync_service: Arc<SyncService>,
55 status_handle: StatusHandle,
56 ) -> Self {
57 Self {
58 client,
59 state: Default::default(),
60 rooms,
61 status_handle,
62 room_infos,
63 current_room_subscription: None,
64 sync_service,
65 }
66 }
67
68 pub async fn next_room(&mut self) {
72 let num_items = self.rooms.lock().len();
73
74 if num_items == 0 {
76 self.state.select(None);
77 return;
78 }
79
80 let prev = self.state.selected();
82 let new = prev.map_or(0, |i| if i >= num_items - 1 { 0 } else { i + 1 });
83
84 if prev != Some(new) {
85 self.state.select(Some(new));
86 self.subscribe_to_room(new).await;
87 }
88 }
89
90 pub async fn previous_room(&mut self) {
94 let num_items = self.rooms.lock().len();
95
96 if num_items == 0 {
98 self.state.select(None);
99 return;
100 }
101
102 let prev = self.state.selected();
104 let new = prev.map_or(0, |i| if i == 0 { num_items - 1 } else { i - 1 });
105
106 if prev != Some(new) {
107 self.state.select(Some(new));
108 self.subscribe_to_room(new).await;
109 }
110 }
111
112 pub fn get_room_id_of_entry(&self, nth: usize) -> Option<OwnedRoomId> {
114 self.rooms.lock().get(nth).cloned().map(|room| room.room_id().to_owned())
115 }
116
117 pub fn get_selected_room_id(&self) -> Option<OwnedRoomId> {
119 let selected = self.state.selected()?;
120 self.get_room_id_of_entry(selected)
121 }
122
123 async fn subscribe_to_room(&mut self, index: usize) {
125 self.current_room_subscription.take();
127
128 if let Some(room) =
130 self.get_room_id_of_entry(index).and_then(|room_id| self.client.get_room(&room_id))
131 {
132 self.sync_service.room_list_service().subscribe_to_rooms(&[room.room_id()]).await;
133 self.current_room_subscription = Some(room);
134 }
135 }
136}
137
138impl Widget for &mut RoomList {
139 fn render(self, area: Rect, buf: &mut Buffer)
140 where
141 Self: Sized,
142 {
143 let outer_block = Block::default()
146 .borders(Borders::RIGHT)
147 .border_set(symbols::border::THICK)
148 .fg(TEXT_COLOR)
149 .bg(HEADER_BG)
150 .title("Room list")
151 .title_alignment(Alignment::Center);
152 let inner_block =
153 Block::default().borders(Borders::NONE).fg(TEXT_COLOR).bg(NORMAL_ROW_COLOR);
154
155 let outer_area = area;
158 let inner_area = outer_block.inner(outer_area);
159
160 outer_block.render(outer_area, buf);
162
163 let mut room_info = self.room_infos.lock().clone();
166
167 let items: Vec<ListItem<'_>> = self
169 .rooms
170 .lock()
171 .iter()
172 .enumerate()
173 .map(|(i, room)| {
174 let bg_color = match i % 2 {
175 0 => NORMAL_ROW_COLOR,
176 _ => ALT_ROW_COLOR,
177 };
178
179 let line = {
180 let room_id = room.room_id();
181 let room_info = room_info.remove(room_id);
182
183 let (raw, display, is_dm) = if let Some(info) = room_info {
184 (info.raw_name, info.display_name, info.is_dm)
185 } else {
186 (None, None, None)
187 };
188
189 let dm_marker = if is_dm.unwrap_or(false) { "🤫" } else { "" };
190
191 let room_name = if let Some(n) = display {
192 format!("{n} ({room_id})")
193 } else if let Some(n) = raw {
194 format!("m.room.name:{n} ({room_id})")
195 } else {
196 room_id.to_string()
197 };
198
199 format!("#{i}{dm_marker} {room_name}")
200 };
201
202 let line = Line::styled(line, TEXT_COLOR);
203 ListItem::new(line).bg(bg_color)
204 })
205 .collect();
206
207 let items = List::new(items)
209 .block(inner_block)
210 .highlight_style(
211 Style::default()
212 .add_modifier(Modifier::BOLD)
213 .add_modifier(Modifier::REVERSED)
214 .fg(SELECTED_STYLE_FG),
215 )
216 .highlight_symbol(">")
217 .highlight_spacing(HighlightSpacing::Always);
218
219 StatefulWidget::render(items, inner_area, buf, &mut self.state);
220 }
221}