buflist: Show rooms with unread events in red
All checks were successful
CI / lint (push) Successful in 2m27s
CI / Build and test (, 1.73.0) (push) Successful in 5m15s
CI / Build and test (, beta) (push) Successful in 6m22s
CI / Build and test (, nightly) (push) Successful in 5m33s

This commit is contained in:
2023-11-19 17:12:55 +01:00
parent 8d6a076b59
commit b60834ebb3
6 changed files with 113 additions and 20 deletions

View File

@ -102,4 +102,9 @@ impl Buffer for LogBuffer {
fn unread_notification_counts(&self) -> matrix_sdk::sync::UnreadNotificationsCount {
Default::default()
}
fn fully_read(&self) -> bool {
// TODO
true
}
}

View File

@ -120,6 +120,7 @@ pub trait Buffer: Send + Sync + memuse::DynamicUsage {
fn request_back_pagination(&self, num: u16) {}
fn unread_notification_counts(&self) -> matrix_sdk::sync::UnreadNotificationsCount;
fn fully_read(&self) -> bool;
fn dynamic_usage(&self) -> usize {
memuse::DynamicUsage::dynamic_usage(self)

View File

@ -29,9 +29,11 @@ use itertools::Itertools;
use matrix_sdk::async_trait;
use matrix_sdk::deserialized_responses::SyncOrStrippedState;
use matrix_sdk::room::ParentSpace;
use matrix_sdk::ruma::events::fully_read::FullyReadEventContent;
use matrix_sdk::ruma::events::space::child::SpaceChildEventContent;
use matrix_sdk::ruma::events::RoomAccountDataEvent;
use matrix_sdk::ruma::events::SyncStateEvent;
use matrix_sdk::ruma::{OwnedRoomId, RoomId};
use matrix_sdk::ruma::{OwnedEventId, OwnedRoomId, RoomId};
use matrix_sdk::sync::UnreadNotificationsCount;
use matrix_sdk::{Client, DisplayName, Room, RoomInfo};
use matrix_sdk_ui::timeline::{
@ -91,7 +93,9 @@ pub struct SingleClientRoomBuffer {
room_id: OwnedRoomId,
client: Client,
/// Oldest first
items: imbl::vector::Vector<(Option<u64>, OwnedBufferItemContent, Prerender)>,
latest_event_id: Option<OwnedEventId>,
// TODO: get rid of this trait object, we know it's matrix_sdk_ui::timeline::TimelineStream
timeline_stream: Box<dyn Stream<Item = Vec<VectorDiff<Arc<TimelineItem>>>> + Send + Sync + Unpin>,
timeline: Arc<Timeline>,
@ -149,6 +153,7 @@ impl SingleClientRoomBuffer {
.map(|item| (Some(item.unique_id()), self.format_timeline_item(item), Prerender::new()))
.apply(&mut self.items);
}
self.latest_event_id = self.timeline.latest_event().await.and_then(|e| e.event_id().map(ToOwned::to_owned));
None
}
}
@ -322,14 +327,14 @@ impl SingleClientRoomBuffer {
},
BackPaginationStatus::Idle => {
tokio::spawn(async move {
tracing::info!("Starting pagination for {}", room_id);
tracing::debug!("Starting pagination for {}", room_id);
timeline
.paginate_backwards(matrix_sdk_ui::timeline::PaginationOptions::until_num_items(
num, num,
))
.await
.unwrap_or_else(|e| tracing::error!("Failed to paginate {} backward: {}", room_id, e));
tracing::info!("Ended pagination for {}", room_id);
tracing::debug!("Ended pagination for {}", room_id);
});
// Wait for the task we just spawned to change the status, so we don't risk starting
@ -409,6 +414,7 @@ impl RoomBuffer {
.into_iter()
.map(|item| (None, text!("Initial item: {:#?}", item), Prerender::new()))
.collect(),
latest_event_id: None,
timeline_stream: Box::new(timeline_stream),
back_pagination_request: AtomicU16::new(0),
roominfo_subscriber: room.subscribe_info(),
@ -480,6 +486,7 @@ impl Buffer for RoomBuffer {
tokio::spawn(compute_room_info(room, tx, roominfo_hash));
}
// Poll for new updates from the server
let mut roominfo_update = self
.buffers
.iter_mut()
@ -489,6 +496,7 @@ impl Buffer for RoomBuffer {
})
.collect::<FuturesUnordered<_>>();
// Wait for result of processing new updates
let update_roominfo_rx = async {
match self.update_roominfo_rx.as_mut() {
Some(update_roominfo_rx) => Some(
@ -578,6 +586,42 @@ impl Buffer for RoomBuffer {
}
acc
}
fn fully_read(&self) -> bool {
match self
.computed_roominfo
.as_ref()
.and_then(|ri| ri.fully_read_at.as_ref())
{
None => true, // Unknown, assume it's read for now, we'll probably find out later
Some(None) => false, // Nothing is read
Some(Some(fully_read_at)) => {
// Iterate through all buffers, and if any buffer's last event is not the one where
// the m.fully_read marker is at, assume the buffer is not read.
//
// Note that, if we joined the room with more than one account and two accounts'
// timelines aren't perfectly in sync, then the marker is guaranteed not to match
// the last event of at least one.
// Technically, this is a bug. However this shouldn't be a big deal in practice as
// such a desync usually means the message is very recent, hence unread. (Or it's
// a federation desync, and we should probably tell the user about it somehow. But
// this is out of scope for this function.)
for buf in &self.buffers {
let Some(latest_event_id) = &buf.latest_event_id else {
// This buffer contains no event at all, request a small backfill and assume
// it is read for now.
buf.back_pagination_request.fetch_max(1, Ordering::Relaxed);
continue;
};
if latest_event_id != fully_read_at {
return false;
}
}
true
},
}
}
}
async fn compute_room_info(
@ -585,7 +629,7 @@ async fn compute_room_info(
done_tx: oneshot::Sender<ComputedRoomInfo>,
roominfo_hash: u64,
) {
let (parent, children, display_name) = tokio::join!(
let (parent, children, fully_read_at, display_name) = tokio::join!(
async {
// Get parent space
match room.parent_spaces().await {
@ -649,6 +693,29 @@ async fn compute_room_info(
},
}
},
async {
// Get m.fully_read
match room.account_data_static::<FullyReadEventContent>().await {
Ok(Some(event)) => match event.deserialize() {
Ok(RoomAccountDataEvent {
content: FullyReadEventContent { event_id, .. },
}) => Some(Some(event_id)),
Err(e) => {
tracing::error!(
"Failed to deserialize m.fully_read for {}: {:?}",
room.room_id(),
e
);
None
},
},
Ok(None) => Some(None), // Nothing in the room is read
Err(e) => {
tracing::error!("Failed to get m.fully_read for {}: {:?}", room.room_id(), e);
None
},
}
},
async {
match room.display_name().await {
Ok(dn) => {
@ -675,6 +742,7 @@ async fn compute_room_info(
display_name,
parent,
children,
fully_read_at,
roominfo_hash,
};
done_tx
@ -687,6 +755,9 @@ struct ComputedRoomInfo {
display_name: Option<DisplayName>,
parent: Option<OwnedRoomId>,
children: Option<SortedVec<(BufferSortKey, OwnedRoomId)>>,
/// `None` if unknown, `Some(None)` if nothing is read, `Some(Some(last_fully_read_event_id))`
/// otherwise
fully_read_at: Option<Option<OwnedEventId>>,
roominfo_hash: u64,
}

View File

@ -61,19 +61,35 @@ impl Buflist {
pub fn get_room_name_style(&self, buf: &dyn Buffer) -> Style {
use matrix_sdk::sync::UnreadNotificationsCount;
let config = &self.config.style.buflist;
match buf.unread_notification_counts() {
UnreadNotificationsCount {
highlight_count: 0,
notification_count: 0,
} => *config.uneventful,
UnreadNotificationsCount {
highlight_count: 0,
notification_count: _,
} => *config.notification,
UnreadNotificationsCount {
highlight_count: _,
notification_count: _,
} => *config.highlight,
match (buf.fully_read(), buf.unread_notification_counts()) {
(
true,
UnreadNotificationsCount {
highlight_count: 0,
notification_count: 0,
},
) => *config.uneventful,
(
_,
UnreadNotificationsCount {
highlight_count: 0,
notification_count: 0,
},
) => *config.unread_message,
(
_,
UnreadNotificationsCount {
highlight_count: 0,
notification_count: _,
},
) => *config.notification,
(
_,
UnreadNotificationsCount {
highlight_count: _,
notification_count: _,
},
) => *config.highlight,
}
}
}

View File

@ -67,7 +67,7 @@ pub struct LayoutConfig {
pub struct BuflistStylesConfig {
pub highlight: StyleConfig,
pub notification: StyleConfig,
// TODO: pub unread_message: StyleConfig,
pub unread_message: StyleConfig,
// TODO: pub unread_event: StyleConfig,
pub uneventful: StyleConfig,
}

View File

@ -31,8 +31,8 @@ min_width = 30
highlight = "bold yellow"
# On other types of notifications
notification = "bold green"
# TODO: When no notification, but there is a new message
# unread_message = "red"
# When no notification, but there is a new message
unread_message = "red"
# TODO: When no new message, but something changed (someone joined, left, topic changed, ...)
# unread_event = "bold"
# If nothing happened since you last looked at the room