mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
parent
3ed87aae05
commit
a2a708f1ae
7 changed files with 271 additions and 64 deletions
55
src/base.rs
55
src/base.rs
|
@ -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) {
|
||||||
|
|
|
@ -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),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
25
src/main.rs
25
src/main.rs
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue