refactor(multiverse): Turn the read receipt rendering logic into a widget

This commit is contained in:
Damir Jelić
2025-03-11 16:01:56 +01:00
parent fb4caf40aa
commit f31119a013
3 changed files with 131 additions and 58 deletions

View File

@@ -11,13 +11,13 @@ use color_eyre::Result;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use futures_util::{pin_mut, StreamExt as _};
use imbl::Vector;
use layout::Flex;
use matrix_sdk::{
authentication::matrix::MatrixSession,
config::StoreConfig,
encryption::{BackupDownloadStrategy, EncryptionSettings},
reqwest::Url,
ruma::{
api::client::receipt::create_receipt::v3::ReceiptType,
events::room::message::{MessageType, RoomMessageEventContent},
MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId,
},
@@ -34,11 +34,13 @@ use matrix_sdk_ui::{
Timeline as SdkTimeline,
};
use ratatui::{prelude::*, style::palette::tailwind, widgets::*};
use read_receipts::ReadReceipts;
use status::Status;
use tokio::{runtime::Handle, spawn, task::JoinHandle};
use tracing::{error, warn};
use tracing_subscriber::EnvFilter;
mod read_receipts;
mod room_list;
mod status;
@@ -67,6 +69,16 @@ struct Cli {
proxy: Option<Url>,
}
/// Helper function to create a centered rect using up certain percentage of the
/// available rect `r`
fn popup_area(area: Rect, percent_x: u16, percent_y: u16) -> Rect {
let vertical = Layout::vertical([Constraint::Percentage(percent_y)]).flex(Flex::Center);
let horizontal = Layout::horizontal([Constraint::Percentage(percent_x)]).flex(Flex::Center);
let [area] = vertical.areas(area);
let [area] = horizontal.areas(area);
area
}
#[tokio::main]
async fn main() -> Result<()> {
let file_writer = tracing_appender::rolling::hourly("/tmp/", "logs-");
@@ -159,8 +171,14 @@ impl App {
// stopped.
sync_service.start().await;
let room_list = RoomList::new(rooms, ui_rooms.clone(), room_infos, sync_service.clone());
let status = Status::new();
let room_list = RoomList::new(
rooms,
ui_rooms.clone(),
room_infos,
sync_service.clone(),
status.handle(),
);
Ok(Self {
sync_service,
@@ -277,31 +295,6 @@ impl App {
}
}
/// Mark the currently selected room as read.
async fn mark_as_read(&mut self) {
let Some(room) = self
.room_list
.get_selected_room_id()
.and_then(|room_id| self.ui_rooms.lock().get(&room_id).cloned())
else {
self.status.set_message("missing room or nothing to show".to_owned());
return;
};
// Mark as read!
match room.timeline().unwrap().mark_as_read(ReceiptType::Read).await {
Ok(did) => {
self.status.set_message(format!(
"did {}send a read receipt!",
if did { "" } else { "not " }
));
}
Err(err) => {
self.status.set_message(format!("error when marking a room as read: {err}",));
}
}
}
async fn toggle_reaction_to_latest_msg(&mut self) {
let selected = self.room_list.get_selected_room_id();
@@ -433,7 +426,11 @@ impl App {
Char('L') => self.toggle_reaction_to_latest_msg().await,
Char('r') => self.set_mode(DetailsMode::ReadReceipts),
Char('r') => {
if self.room_list.get_selected_room_id().is_some() {
self.set_mode(DetailsMode::ReadReceipts);
}
}
Char('t') => self.set_mode(DetailsMode::TimelineItems),
Char('e') => self.set_mode(DetailsMode::Events),
Char('l') => self.set_mode(DetailsMode::LinkedChunk),
@@ -446,7 +443,7 @@ impl App {
}
Char('m') if self.details_mode == DetailsMode::ReadReceipts => {
self.mark_as_read().await
self.room_list.mark_as_read().await
}
_ => {}
@@ -542,35 +539,14 @@ impl App {
if let Some(room_id) = self.room_list.get_selected_room_id() {
match self.details_mode {
DetailsMode::ReadReceipts => {
// In read receipts mode, show the read receipts object as computed
// by the client.
match self.ui_rooms.lock().get(&room_id).cloned() {
Some(room) => {
let receipts = room.read_receipts();
render_paragraph(
buf,
format!(
r#"Read receipts:
- unread: {}
- notifications: {}
- mentions: {}
// In read receipts mode, show the read receipts object as computed by the
// client.
let rooms = self.ui_rooms.lock();
let room = rooms.get(&room_id);
---
let mut read_receipts = ReadReceipts::new(room);
{:?}
"#,
receipts.num_unread,
receipts.num_notifications,
receipts.num_mentions,
receipts
),
)
}
None => render_paragraph(
buf,
"(room disappeared in the room list service)".to_owned(),
),
}
read_receipts.render(inner_area, buf);
}
DetailsMode::TimelineItems => {

View File

@@ -0,0 +1,65 @@
use matrix_sdk_ui::room_list_service::Room;
use ratatui::{
prelude::*,
widgets::{Block, Clear, Paragraph, Wrap},
};
use crate::{popup_area, TEXT_COLOR};
pub struct ReadReceipts<'a> {
room: Option<&'a Room>,
}
impl<'a> ReadReceipts<'a> {
pub fn new(room: Option<&'a Room>) -> Self {
Self { room }
}
}
impl Widget for &mut ReadReceipts<'_> {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
let read_receipt_block = Block::bordered().title("Read receipts");
let read_receipt_area = popup_area(area, 80, 80);
Clear.render(read_receipt_area, buf);
match self.room {
Some(room) => {
let receipts = room.read_receipts();
let content = format!(
r#"Read receipts:
- unread: {}
- notifications: {}
- mentions: {}
---
{:?}
"#,
receipts.num_unread,
receipts.num_notifications,
receipts.num_mentions,
receipts
);
Paragraph::new(content)
.block(read_receipt_block.clone())
.fg(TEXT_COLOR)
.wrap(Wrap { trim: false })
.render(read_receipt_area, buf);
}
None => {
let content = "(room disappeared in the room list service)";
Paragraph::new(content)
.block(read_receipt_block.clone())
.fg(TEXT_COLOR)
.wrap(Wrap { trim: false })
.render(read_receipt_area, buf);
}
}
read_receipt_block.render(read_receipt_area, buf);
}
}

View File

@@ -4,11 +4,14 @@ use std::{
};
use imbl::Vector;
use matrix_sdk::ruma::OwnedRoomId;
use matrix_sdk::ruma::{api::client::receipt::create_receipt::v3::ReceiptType, OwnedRoomId};
use matrix_sdk_ui::{room_list_service, sync_service::SyncService};
use ratatui::{prelude::*, widgets::*};
use crate::{UiRooms, ALT_ROW_COLOR, HEADER_BG, NORMAL_ROW_COLOR, SELECTED_STYLE_FG, TEXT_COLOR};
use crate::{
status::StatusHandle, UiRooms, ALT_ROW_COLOR, HEADER_BG, NORMAL_ROW_COLOR, SELECTED_STYLE_FG,
TEXT_COLOR,
};
/// Extra room information, like its display name, etc.
#[derive(Clone)]
@@ -29,6 +32,8 @@ pub type RoomInfos = Arc<Mutex<HashMap<OwnedRoomId, ExtraRoomInfo>>>;
pub struct RoomList {
pub state: ListState,
pub status_handle: StatusHandle,
pub rooms: Rooms,
/// Room list service rooms known to the app.
@@ -50,10 +55,12 @@ impl RoomList {
ui_rooms: UiRooms,
room_infos: RoomInfos,
sync_service: Arc<SyncService>,
status_handle: StatusHandle,
) -> Self {
Self {
state: Default::default(),
rooms,
status_handle,
room_infos,
current_room_subscription: None,
ui_rooms,
@@ -130,6 +137,31 @@ impl RoomList {
self.current_room_subscription = Some(room);
}
}
/// Mark the currently selected room as read.
pub async fn mark_as_read(&mut self) {
let Some(room) = self
.get_selected_room_id()
.and_then(|room_id| self.ui_rooms.lock().get(&room_id).cloned())
else {
self.status_handle.set_message("missing room or nothing to show".to_owned());
return;
};
// Mark as read!
match room.timeline().unwrap().mark_as_read(ReceiptType::Read).await {
Ok(did) => {
self.status_handle.set_message(format!(
"did {}send a read receipt!",
if did { "" } else { "not " }
));
}
Err(err) => {
self.status_handle
.set_message(format!("error when marking a room as read: {err}",));
}
}
}
}
impl Widget for &mut RoomList {