Indicate and sort on rooms with unread messages (#205)

Fixes #83
This commit is contained in:
Ulyssa 2024-02-28 09:03:28 -08:00 committed by GitHub
parent 3ed87aae05
commit a2a708f1ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 271 additions and 64 deletions

View file

@ -53,6 +53,7 @@ use matrix_sdk::{
OwnedRoomId, OwnedRoomId,
OwnedUserId, OwnedUserId,
RoomId, RoomId,
UserId,
}, },
}; };
@ -209,11 +210,26 @@ bitflags::bitflags! {
/// Fields that rooms and spaces can be sorted by. /// Fields that rooms and spaces can be sorted by.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum SortFieldRoom { pub enum SortFieldRoom {
/// Sort rooms by whether they have the Favorite tag.
Favorite, Favorite,
/// Sort rooms by whether they have the Low Priority tag.
LowPriority, LowPriority,
/// Sort rooms by their room name.
Name, Name,
/// Sort rooms by their canonical room alias.
Alias, Alias,
/// Sort rooms by their Matrix room identifier.
RoomId, RoomId,
/// Sort rooms by whether they have unread messages.
Unread,
/// Sort rooms by the timestamps of their most recent messages.
Recent,
} }
/// Fields that users can be sorted by. /// Fields that users can be sorted by.
@ -273,6 +289,8 @@ impl<'de> Visitor<'de> for SortRoomVisitor {
let field = match value { let field = match value {
"favorite" => SortFieldRoom::Favorite, "favorite" => SortFieldRoom::Favorite,
"lowpriority" => SortFieldRoom::LowPriority, "lowpriority" => SortFieldRoom::LowPriority,
"recent" => SortFieldRoom::Recent,
"unread" => SortFieldRoom::Unread,
"name" => SortFieldRoom::Name, "name" => SortFieldRoom::Name,
"alias" => SortFieldRoom::Alias, "alias" => SortFieldRoom::Alias,
"id" => SortFieldRoom::RoomId, "id" => SortFieldRoom::RoomId,
@ -672,6 +690,22 @@ impl EventLocation {
} }
} }
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct UnreadInfo {
pub(crate) unread: bool,
pub(crate) latest: Option<MessageTimeStamp>,
}
impl UnreadInfo {
pub fn is_unread(&self) -> bool {
self.unread
}
pub fn latest(&self) -> Option<&MessageTimeStamp> {
self.latest.as_ref()
}
}
/// Information about room's the user's joined. /// Information about room's the user's joined.
#[derive(Default)] #[derive(Default)]
pub struct RoomInfo { pub struct RoomInfo {
@ -697,9 +731,6 @@ pub struct RoomInfo {
/// older than the oldest loaded event, that user will not be included. /// older than the oldest loaded event, that user will not be included.
pub user_receipts: HashMap<OwnedUserId, OwnedEventId>, pub user_receipts: HashMap<OwnedUserId, OwnedEventId>,
/// An event ID for where we should indicate we've read up to.
pub read_till: Option<OwnedEventId>,
/// A map of message identifiers to a map of reaction events. /// A map of message identifiers to a map of reaction events.
pub reactions: HashMap<OwnedEventId, MessageReactions>, pub reactions: HashMap<OwnedEventId, MessageReactions>,
@ -810,6 +841,20 @@ impl RoomInfo {
msg.html = msg.event.html(); msg.html = msg.event.html();
} }
/// Indicates whether this room has unread messages.
pub fn unreads(&self, settings: &ApplicationSettings) -> UnreadInfo {
let last_message = self.messages.last_key_value();
let last_receipt = self.get_receipt(&settings.profile.user_id);
match (last_message, last_receipt) {
(Some(((ts, recent), _)), Some(last_read)) => {
UnreadInfo { unread: last_read != recent, latest: Some(*ts) }
},
(Some(((ts, _), _)), None) => UnreadInfo { unread: false, latest: Some(*ts) },
(None, _) => UnreadInfo::default(),
}
}
/// Inserts events that couldn't be decrypted into the scrollback. /// Inserts events that couldn't be decrypted into the scrollback.
pub fn insert_encrypted(&mut self, msg: RoomEncryptedEvent) { pub fn insert_encrypted(&mut self, msg: RoomEncryptedEvent) {
let event_id = msg.event_id().to_owned(); let event_id = msg.event_id().to_owned();
@ -901,6 +946,10 @@ impl RoomInfo {
self.user_receipts.insert(user_id, event_id); self.user_receipts.insert(user_id, event_id);
} }
pub fn get_receipt(&self, user_id: &UserId) -> Option<&OwnedEventId> {
self.user_receipts.get(user_id)
}
fn get_typers(&self) -> &[OwnedUserId] { fn get_typers(&self) -> &[OwnedUserId] {
if let Some((t, users)) = &self.users_typing { if let Some((t, users)) = &self.users_typing {
if t.elapsed() < Duration::from_secs(4) { if t.elapsed() < Duration::from_secs(4) {

View file

@ -32,9 +32,10 @@ const DEFAULT_MEMBERS_SORT: [SortColumn<SortFieldUser>; 2] = [
SortColumn(SortFieldUser::UserId, SortOrder::Ascending), SortColumn(SortFieldUser::UserId, SortOrder::Ascending),
]; ];
const DEFAULT_ROOM_SORT: [SortColumn<SortFieldRoom>; 3] = [ const DEFAULT_ROOM_SORT: [SortColumn<SortFieldRoom>; 4] = [
SortColumn(SortFieldRoom::Favorite, SortOrder::Ascending), SortColumn(SortFieldRoom::Favorite, SortOrder::Ascending),
SortColumn(SortFieldRoom::LowPriority, SortOrder::Ascending), SortColumn(SortFieldRoom::LowPriority, SortOrder::Ascending),
SortColumn(SortFieldRoom::Unread, SortOrder::Ascending),
SortColumn(SortFieldRoom::Name, SortOrder::Ascending), SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
]; ];

View file

@ -27,17 +27,10 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use clap::Parser; use clap::Parser;
use matrix_sdk::ruma::OwnedUserId;
use tokio::sync::Mutex as AsyncMutex; use tokio::sync::Mutex as AsyncMutex;
use tracing_subscriber::FmtSubscriber; use tracing_subscriber::FmtSubscriber;
use matrix_sdk::{
config::SyncSettings,
ruma::{
api::client::filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
OwnedUserId,
},
};
use modalkit::crossterm::{ use modalkit::crossterm::{
self, self,
cursor::Show as CursorShow, cursor::Show as CursorShow,
@ -719,20 +712,6 @@ async fn login(worker: Requester, settings: &ApplicationSettings) -> IambResult<
} }
} }
// Perform an initial, lazily-loaded sync.
let mut room = RoomEventFilter::default();
room.lazy_load_options = LazyLoadOptions::Enabled { include_redundant_members: false };
let mut room_ev = RoomFilter::default();
room_ev.state = room;
let mut filter = FilterDefinition::default();
filter.room = room_ev;
let settings = SyncSettings::new().filter(filter.into());
worker.client.sync_once(settings).await.map_err(IambError::from)?;
Ok(()) Ok(())
} }
@ -744,12 +723,14 @@ fn print_exit<T: Display, N>(v: T) -> N {
async fn run(settings: ApplicationSettings) -> IambResult<()> { async fn run(settings: ApplicationSettings) -> IambResult<()> {
// Set up the async worker thread and global store. // Set up the async worker thread and global store.
let worker = ClientWorker::spawn(settings.clone()).await; let worker = ClientWorker::spawn(settings.clone()).await;
let client = worker.client.clone();
let store = ChatStore::new(worker.clone(), settings.clone()); let store = ChatStore::new(worker.clone(), settings.clone());
let store = Store::new(store); let store = Store::new(store);
let store = Arc::new(AsyncMutex::new(store)); let store = Arc::new(AsyncMutex::new(store));
worker.init(store.clone()); worker.init(store.clone());
login(worker, &settings).await.unwrap_or_else(print_exit); login(worker, &settings).await.unwrap_or_else(print_exit);
worker::do_first_sync(client, &store).await;
fn restore_tty() { fn restore_tty() {
let _ = crossterm::terminal::disable_raw_mode(); let _ = crossterm::terminal::disable_raw_mode();

View file

@ -156,7 +156,6 @@ pub fn mock_room() -> RoomInfo {
event_receipts: HashMap::new(), event_receipts: HashMap::new(),
user_receipts: HashMap::new(), user_receipts: HashMap::new(),
read_till: None,
reactions: HashMap::new(), reactions: HashMap::new(),
fetching: false, fetching: false,

View file

@ -79,9 +79,11 @@ use crate::base::{
SortFieldRoom, SortFieldRoom,
SortFieldUser, SortFieldUser,
SortOrder, SortOrder,
UnreadInfo,
}; };
use self::{room::RoomState, welcome::WelcomeState}; use self::{room::RoomState, welcome::WelcomeState};
use crate::message::MessageTimeStamp;
pub mod room; pub mod room;
pub mod welcome; pub mod welcome;
@ -124,11 +126,31 @@ fn selected_text(s: &str, selected: bool) -> Text {
Text::from(selected_span(s, selected)) Text::from(selected_span(s, selected))
} }
fn name_and_labels(name: &str, unread: bool, style: Style) -> (Span<'_>, Vec<Vec<Span<'_>>>) {
let name_style = if unread {
style.add_modifier(StyleModifier::BOLD)
} else {
style
};
let name = Span::styled(name, name_style);
let labels = if unread {
vec![vec![Span::styled("Unread", style)]]
} else {
vec![]
};
(name, labels)
}
/// Sort `Some` to be less than `None` so that list items with values come before those without. /// Sort `Some` to be less than `None` so that list items with values come before those without.
#[inline] #[inline]
fn some_cmp<T: Ord>(a: Option<T>, b: Option<T>) -> Ordering { fn some_cmp<T, F>(a: Option<T>, b: Option<T>, f: F) -> Ordering
where
F: Fn(&T, &T) -> Ordering,
{
match (a, b) { match (a, b) {
(Some(a), Some(b)) => a.cmp(&b), (Some(a), Some(b)) => f(&a, &b),
(None, None) => Ordering::Equal, (None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater, (None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less, (Some(_), None) => Ordering::Less,
@ -167,8 +189,16 @@ fn room_cmp<T: RoomLikeItem>(a: &T, b: &T, field: &SortFieldRoom) -> Ordering {
lowa.cmp(&lowb) lowa.cmp(&lowb)
}, },
SortFieldRoom::Name => a.name().cmp(b.name()), SortFieldRoom::Name => a.name().cmp(b.name()),
SortFieldRoom::Alias => some_cmp(a.alias(), b.alias()), SortFieldRoom::Alias => some_cmp(a.alias(), b.alias(), Ord::cmp),
SortFieldRoom::RoomId => a.room_id().cmp(b.room_id()), SortFieldRoom::RoomId => a.room_id().cmp(b.room_id()),
SortFieldRoom::Unread => {
// Sort true (unread) before false (read)
b.is_unread().cmp(&a.is_unread())
},
SortFieldRoom::Recent => {
// sort larger timestamps towards the top.
some_cmp(a.recent_ts(), b.recent_ts(), |a, b| b.cmp(a))
},
} }
} }
@ -243,6 +273,8 @@ fn append_tags<'a>(tags: Vec<Vec<Span<'a>>>, spans: &mut Vec<Span<'a>>, style: S
trait RoomLikeItem { trait RoomLikeItem {
fn room_id(&self) -> &RoomId; fn room_id(&self) -> &RoomId;
fn has_tag(&self, tag: TagName) -> bool; fn has_tag(&self, tag: TagName) -> bool;
fn is_unread(&self) -> bool;
fn recent_ts(&self) -> Option<&MessageTimeStamp>;
fn alias(&self) -> Option<&RoomAliasId>; fn alias(&self) -> Option<&RoomAliasId>;
fn name(&self) -> &str; fn name(&self) -> &str;
} }
@ -780,6 +812,7 @@ pub struct GenericChatItem {
room_info: MatrixRoomInfo, room_info: MatrixRoomInfo,
name: String, name: String,
alias: Option<OwnedRoomAliasId>, alias: Option<OwnedRoomAliasId>,
unread: UnreadInfo,
is_dm: bool, is_dm: bool,
} }
@ -788,17 +821,17 @@ impl GenericChatItem {
let room = &room_info.deref().0; let room = &room_info.deref().0;
let room_id = room.room_id(); let room_id = room.room_id();
let info = store.application.get_room_info(room_id.to_owned()); let info = store.application.rooms.get_or_default(room_id.to_owned());
let name = info.name.clone().unwrap_or_default(); let name = info.name.clone().unwrap_or_default();
let alias = room.canonical_alias(); let alias = room.canonical_alias();
let unread = info.unreads(&store.application.settings);
info.tags = room_info.deref().1.clone(); info.tags = room_info.deref().1.clone();
if let Some(alias) = &alias { if let Some(alias) = &alias {
store.application.names.insert(alias.to_string(), room_id.to_owned()); store.application.names.insert(alias.to_string(), room_id.to_owned());
} }
GenericChatItem { room_info, name, alias, is_dm } GenericChatItem { room_info, name, alias, is_dm, unread }
} }
#[inline] #[inline]
@ -832,6 +865,14 @@ impl RoomLikeItem for GenericChatItem {
false false
} }
} }
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
self.unread.latest()
}
fn is_unread(&self) -> bool {
self.unread.is_unread()
}
} }
impl ToString for GenericChatItem { impl ToString for GenericChatItem {
@ -842,13 +883,16 @@ impl ToString for GenericChatItem {
impl ListItem<IambInfo> for GenericChatItem { impl ListItem<IambInfo> for GenericChatItem {
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text { fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
let unread = self.unread.is_unread();
let style = selected_style(selected); let style = selected_style(selected);
let mut spans = vec![Span::styled(self.name.as_str(), style)]; let (name, mut labels) = name_and_labels(&self.name, unread, style);
let mut labels = if self.is_dm { let mut spans = vec![name];
vec![vec![Span::styled("DM", style)]]
labels.push(if self.is_dm {
vec![Span::styled("DM", style)]
} else { } else {
vec![vec![Span::styled("Room", style)]] vec![Span::styled("Room", style)]
}; });
if let Some(tags) = &self.tags() { if let Some(tags) = &self.tags() {
labels.extend(tags.keys().map(|t| tag_to_span(t, style))); labels.extend(tags.keys().map(|t| tag_to_span(t, style)));
@ -879,6 +923,7 @@ pub struct RoomItem {
room_info: MatrixRoomInfo, room_info: MatrixRoomInfo,
name: String, name: String,
alias: Option<OwnedRoomAliasId>, alias: Option<OwnedRoomAliasId>,
unread: UnreadInfo,
} }
impl RoomItem { impl RoomItem {
@ -886,16 +931,17 @@ impl RoomItem {
let room = &room_info.deref().0; let room = &room_info.deref().0;
let room_id = room.room_id(); let room_id = room.room_id();
let info = store.application.get_room_info(room_id.to_owned()); let info = store.application.rooms.get_or_default(room_id.to_owned());
let name = info.name.clone().unwrap_or_default(); let name = info.name.clone().unwrap_or_default();
let alias = room.canonical_alias(); let alias = room.canonical_alias();
let unread = info.unreads(&store.application.settings);
info.tags = room_info.deref().1.clone(); info.tags = room_info.deref().1.clone();
if let Some(alias) = &alias { if let Some(alias) = &alias {
store.application.names.insert(alias.to_string(), room_id.to_owned()); store.application.names.insert(alias.to_string(), room_id.to_owned());
} }
RoomItem { room_info, name, alias } RoomItem { room_info, name, alias, unread }
} }
#[inline] #[inline]
@ -929,6 +975,14 @@ impl RoomLikeItem for RoomItem {
false false
} }
} }
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
self.unread.latest()
}
fn is_unread(&self) -> bool {
self.unread.is_unread()
}
} }
impl ToString for RoomItem { impl ToString for RoomItem {
@ -939,17 +993,18 @@ impl ToString for RoomItem {
impl ListItem<IambInfo> for RoomItem { impl ListItem<IambInfo> for RoomItem {
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text { fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
if let Some(tags) = &self.tags() { let unread = self.unread.is_unread();
let style = selected_style(selected); let style = selected_style(selected);
let mut spans = vec![Span::styled(self.name.as_str(), style)]; let (name, mut labels) = name_and_labels(&self.name, unread, style);
let tags = tags.keys().map(|t| tag_to_span(t, style)).collect(); let mut spans = vec![name];
append_tags(tags, &mut spans, style); if let Some(tags) = &self.tags() {
labels.extend(tags.keys().map(|t| tag_to_span(t, style)));
}
append_tags(labels, &mut spans, style);
Text::from(Line::from(spans)) Text::from(Line::from(spans))
} else {
selected_text(self.name.as_str(), selected)
}
} }
fn get_word(&self) -> Option<String> { fn get_word(&self) -> Option<String> {
@ -973,15 +1028,20 @@ pub struct DirectItem {
room_info: MatrixRoomInfo, room_info: MatrixRoomInfo,
name: String, name: String,
alias: Option<OwnedRoomAliasId>, alias: Option<OwnedRoomAliasId>,
unread: UnreadInfo,
} }
impl DirectItem { impl DirectItem {
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self { fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self {
let room_id = room_info.0.room_id().to_owned(); let room_id = room_info.0.room_id().to_owned();
let name = store.application.get_room_info(room_id).name.clone().unwrap_or_default();
let alias = room_info.0.canonical_alias(); let alias = room_info.0.canonical_alias();
DirectItem { room_info, name, alias } let info = store.application.rooms.get_or_default(room_id);
let name = info.name.clone().unwrap_or_default();
let unread = info.unreads(&store.application.settings);
info.tags = room_info.deref().1.clone();
DirectItem { room_info, name, alias, unread }
} }
#[inline] #[inline]
@ -1015,6 +1075,14 @@ impl RoomLikeItem for DirectItem {
fn room_id(&self) -> &RoomId { fn room_id(&self) -> &RoomId {
self.room().room_id() self.room().room_id()
} }
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
self.unread.latest()
}
fn is_unread(&self) -> bool {
self.unread.is_unread()
}
} }
impl ToString for DirectItem { impl ToString for DirectItem {
@ -1025,17 +1093,18 @@ impl ToString for DirectItem {
impl ListItem<IambInfo> for DirectItem { impl ListItem<IambInfo> for DirectItem {
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text { fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
if let Some(tags) = &self.tags() { let unread = self.unread.is_unread();
let style = selected_style(selected); let style = selected_style(selected);
let mut spans = vec![Span::styled(self.name.as_str(), style)]; let (name, mut labels) = name_and_labels(&self.name, unread, style);
let tags = tags.keys().map(|t| tag_to_span(t, style)).collect(); let mut spans = vec![name];
append_tags(tags, &mut spans, style); if let Some(tags) = &self.tags() {
labels.extend(tags.keys().map(|t| tag_to_span(t, style)));
}
append_tags(labels, &mut spans, style);
Text::from(Line::from(spans)) Text::from(Line::from(spans))
} else {
selected_text(self.name.as_str(), selected)
}
} }
fn get_word(&self) -> Option<String> { fn get_word(&self) -> Option<String> {
@ -1103,6 +1172,16 @@ impl RoomLikeItem for SpaceItem {
// exposes them, so we'll just always return false here for now. // exposes them, so we'll just always return false here for now.
false false
} }
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
// XXX: this needs to determine the room with most recent message and return its timestamp.
None
}
fn is_unread(&self) -> bool {
// XXX: this needs to check whether the space contains rooms with unread messages
false
}
} }
impl ToString for SpaceItem { impl ToString for SpaceItem {
@ -1433,6 +1512,7 @@ mod tests {
tags: Vec<TagName>, tags: Vec<TagName>,
alias: Option<OwnedRoomAliasId>, alias: Option<OwnedRoomAliasId>,
name: &'static str, name: &'static str,
unread: UnreadInfo,
} }
impl RoomLikeItem for &TestRoomItem { impl RoomLikeItem for &TestRoomItem {
@ -1451,6 +1531,14 @@ mod tests {
fn name(&self) -> &str { fn name(&self) -> &str {
self.name self.name
} }
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
self.unread.latest()
}
fn is_unread(&self) -> bool {
self.unread.is_unread()
}
} }
#[test] #[test]
@ -1462,6 +1550,7 @@ mod tests {
tags: vec![TagName::Favorite], tags: vec![TagName::Favorite],
alias: Some(room_alias_id!("#room1:example.com").to_owned()), alias: Some(room_alias_id!("#room1:example.com").to_owned()),
name: "Z", name: "Z",
unread: UnreadInfo::default(),
}; };
let room2 = TestRoomItem { let room2 = TestRoomItem {
@ -1469,6 +1558,7 @@ mod tests {
tags: vec![], tags: vec![],
alias: Some(room_alias_id!("#a:example.com").to_owned()), alias: Some(room_alias_id!("#a:example.com").to_owned()),
name: "Unnamed Room", name: "Unnamed Room",
unread: UnreadInfo::default(),
}; };
let room3 = TestRoomItem { let room3 = TestRoomItem {
@ -1476,6 +1566,7 @@ mod tests {
tags: vec![], tags: vec![],
alias: None, alias: None,
name: "Cool Room", name: "Cool Room",
unread: UnreadInfo::default(),
}; };
// Sort by Name ascending. // Sort by Name ascending.
@ -1510,4 +1601,51 @@ mod tests {
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields)); rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
assert_eq!(rooms, vec![&room2, &room3, &room1]); assert_eq!(rooms, vec![&room2, &room3, &room1]);
} }
#[test]
fn test_sort_room_recents() {
let server = server_name!("example.com");
let room1 = TestRoomItem {
room_id: RoomId::new(server).to_owned(),
tags: vec![],
alias: None,
name: "Room 1",
unread: UnreadInfo { unread: false, latest: None },
};
let room2 = TestRoomItem {
room_id: RoomId::new(server).to_owned(),
tags: vec![],
alias: None,
name: "Room 2",
unread: UnreadInfo {
unread: false,
latest: Some(MessageTimeStamp::OriginServer(40u32.into())),
},
};
let room3 = TestRoomItem {
room_id: RoomId::new(server).to_owned(),
tags: vec![],
alias: None,
name: "Room 3",
unread: UnreadInfo {
unread: false,
latest: Some(MessageTimeStamp::OriginServer(20u32.into())),
},
};
// Sort by Recent ascending.
let mut rooms = vec![&room1, &room2, &room3];
let fields = &[SortColumn(SortFieldRoom::Recent, SortOrder::Ascending)];
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
assert_eq!(rooms, vec![&room2, &room3, &room1]);
// Sort by Recent descending.
let mut rooms = vec![&room1, &room2, &room3];
let fields = &[SortColumn(SortFieldRoom::Recent, SortOrder::Descending)];
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
assert_eq!(rooms, vec![&room1, &room3, &room2]);
}
} }

View file

@ -1342,7 +1342,9 @@ impl<'a> StatefulWidget for Scrollback<'a> {
state.cursor.timestamp.is_none() state.cursor.timestamp.is_none()
{ {
// If the cursor is at the last message, then update the read marker. // If the cursor is at the last message, then update the read marker.
info.read_till = info.messages.last_key_value().map(|(k, _)| k.1.clone()); if let Some((k, _)) = info.messages.last_key_value() {
info.set_receipt(settings.profile.user_id.clone(), k.1.clone());
}
} }
// Check whether we should load older messages for this room. // Check whether we should load older messages for this room.

View file

@ -26,6 +26,7 @@ use matrix_sdk::{
room::{Invited, Messages, MessagesOptions, Room as MatrixRoom, RoomMember}, room::{Invited, Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
ruma::{ ruma::{
api::client::{ api::client::{
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
room::create_room::v3::{CreationContent, Request as CreateRoomRequest, RoomPreset}, room::create_room::v3::{CreationContent, Request as CreateRoomRequest, RoomPreset},
room::Visibility, room::Visibility,
space::get_hierarchy::v1::Request as SpaceHierarchyRequest, space::get_hierarchy::v1::Request as SpaceHierarchyRequest,
@ -424,9 +425,8 @@ async fn refresh_rooms_forever(client: &Client, store: &AsyncProgramStore) {
let mut interval = tokio::time::interval(Duration::from_secs(5)); let mut interval = tokio::time::interval(Duration::from_secs(5));
loop { loop {
interval.tick().await;
refresh_rooms(client, store).await; refresh_rooms(client, store).await;
interval.tick().await;
} }
} }
@ -438,13 +438,14 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) {
interval.tick().await; interval.tick().await;
let locked = store.lock().await; let locked = store.lock().await;
let user_id = &locked.application.settings.profile.user_id;
let updates = client let updates = client
.joined_rooms() .joined_rooms()
.into_iter() .into_iter()
.filter_map(|room| { .filter_map(|room| {
let room_id = room.room_id().to_owned(); let room_id = room.room_id().to_owned();
let info = locked.application.rooms.get(&room_id)?; let info = locked.application.rooms.get(&room_id)?;
let new_receipt = info.read_till.as_ref()?; let new_receipt = info.get_receipt(user_id)?;
let old_receipt = sent.get(&room_id); let old_receipt = sent.get(&room_id);
if Some(new_receipt) != old_receipt { if Some(new_receipt) != old_receipt {
Some((room_id, new_receipt.clone())) Some((room_id, new_receipt.clone()))
@ -469,6 +470,42 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) {
} }
} }
pub async fn do_first_sync(client: Client, store: &AsyncProgramStore) {
// Perform an initial, lazily-loaded sync.
let mut room = RoomEventFilter::default();
room.lazy_load_options = LazyLoadOptions::Enabled { include_redundant_members: false };
let mut room_ev = RoomFilter::default();
room_ev.state = room;
let mut filter = FilterDefinition::default();
filter.room = room_ev;
let settings = SyncSettings::new().filter(filter.into());
if let Err(e) = client.sync_once(settings).await {
tracing::error!(err = e.to_string(), "Failed to perform initial sync; will retry later");
return;
}
// Populate sync_info with our initial set of rooms/dms/spaces.
refresh_rooms(&client, store).await;
// Insert Need::Messages to fetch accurate recent timestamps in the background.
let mut locked = store.lock().await;
let ChatStore { sync_info, need_load, .. } = &mut locked.application;
for room in sync_info.rooms.iter() {
let room_id = room.as_ref().0.room_id().to_owned();
need_load.insert(room_id, Need::MESSAGES);
}
for room in sync_info.dms.iter() {
let room_id = room.as_ref().0.room_id().to_owned();
need_load.insert(room_id, Need::MESSAGES);
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum LoginStyle { pub enum LoginStyle {
SessionRestore(Session), SessionRestore(Session),