2023-10-06 22:35:27 -07:00
|
|
|
//! # Windows for the User Interface
|
|
|
|
//!
|
|
|
|
//! This module contains the logic for rendering windows, and handling UI actions that get
|
|
|
|
//! delegated to individual windows/UI elements (e.g., typing text or selecting a list item).
|
|
|
|
//!
|
|
|
|
//! Additionally, some of the iamb commands delegate behaviour to the current UI element. For
|
|
|
|
//! example, [sending messages][crate::base::SendAction] delegate to the [room window][RoomState],
|
2025-05-15 03:46:13 +02:00
|
|
|
//! where we have the message bar and room ID easily accessible and resettable.
|
2022-12-29 18:00:59 -08:00
|
|
|
use std::cmp::{Ord, Ordering, PartialOrd};
|
2024-08-01 06:02:42 +03:00
|
|
|
use std::fmt::{self, Display};
|
2023-07-05 15:25:42 -07:00
|
|
|
use std::ops::Deref;
|
|
|
|
use std::sync::Arc;
|
2023-03-20 16:01:02 -07:00
|
|
|
use std::time::{Duration, Instant};
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
use matrix_sdk::{
|
|
|
|
encryption::verification::{format_emojis, SasVerification},
|
2023-01-04 12:51:33 -08:00
|
|
|
room::{Room as MatrixRoom, RoomMember},
|
2023-01-25 17:54:16 -08:00
|
|
|
ruma::{
|
|
|
|
events::room::member::MembershipState,
|
|
|
|
events::tag::{TagName, Tags},
|
2023-10-20 19:32:33 -07:00
|
|
|
OwnedRoomAliasId,
|
2023-01-25 17:54:16 -08:00
|
|
|
OwnedRoomId,
|
2023-10-20 19:32:33 -07:00
|
|
|
RoomAliasId,
|
2023-01-25 17:54:16 -08:00
|
|
|
RoomId,
|
|
|
|
},
|
2025-05-15 04:39:22 +02:00
|
|
|
RoomState as MatrixRoomState,
|
2022-12-29 18:00:59 -08:00
|
|
|
};
|
|
|
|
|
2024-02-27 21:21:05 -08:00
|
|
|
use ratatui::{
|
2022-12-29 18:00:59 -08:00
|
|
|
buffer::Buffer,
|
2023-01-04 12:51:33 -08:00
|
|
|
layout::{Alignment, Rect},
|
2022-12-29 18:00:59 -08:00
|
|
|
style::{Modifier as StyleModifier, Style},
|
2023-07-01 08:58:48 +01:00
|
|
|
text::{Line, Span, Text},
|
2023-01-05 18:12:25 -08:00
|
|
|
widgets::StatefulWidget,
|
2022-12-29 18:00:59 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
use modalkit::{
|
2024-02-28 23:00:25 -08:00
|
|
|
actions::{
|
|
|
|
Action,
|
|
|
|
Editable,
|
|
|
|
EditorAction,
|
|
|
|
Jumpable,
|
|
|
|
PromptAction,
|
|
|
|
Promptable,
|
|
|
|
Scrollable,
|
|
|
|
WindowAction,
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
2024-02-28 23:00:25 -08:00
|
|
|
editing::completion::CompletionList,
|
|
|
|
errors::{EditError, EditResult, UIError},
|
2024-02-27 21:21:05 -08:00
|
|
|
prelude::*,
|
|
|
|
};
|
|
|
|
|
|
|
|
use modalkit_ratatui::{
|
|
|
|
list::{List, ListCursor, ListItem, ListState},
|
|
|
|
TermOffset,
|
|
|
|
TerminalCursor,
|
|
|
|
Window,
|
|
|
|
WindowOps,
|
2022-12-29 18:00:59 -08:00
|
|
|
};
|
|
|
|
|
2023-01-06 16:56:28 -08:00
|
|
|
use crate::base::{
|
|
|
|
ChatStore,
|
|
|
|
IambBufferId,
|
2023-01-10 19:59:30 -08:00
|
|
|
IambError,
|
2023-01-06 16:56:28 -08:00
|
|
|
IambId,
|
|
|
|
IambInfo,
|
|
|
|
IambResult,
|
2023-01-10 19:59:30 -08:00
|
|
|
MessageAction,
|
2023-12-18 20:55:04 -08:00
|
|
|
Need,
|
2023-01-06 16:56:28 -08:00
|
|
|
ProgramAction,
|
|
|
|
ProgramContext,
|
|
|
|
ProgramStore,
|
|
|
|
RoomAction,
|
2023-01-10 19:59:30 -08:00
|
|
|
SendAction,
|
2023-10-20 19:32:33 -07:00
|
|
|
SortColumn,
|
|
|
|
SortFieldRoom,
|
|
|
|
SortFieldUser,
|
|
|
|
SortOrder,
|
2025-05-15 03:26:35 +00:00
|
|
|
SpaceAction,
|
2024-02-28 09:03:28 -08:00
|
|
|
UnreadInfo,
|
2022-12-29 18:00:59 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
use self::{room::RoomState, welcome::WelcomeState};
|
2024-02-28 09:03:28 -08:00
|
|
|
use crate::message::MessageTimeStamp;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
pub mod room;
|
|
|
|
pub mod welcome;
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
type MatrixRoomInfo = Arc<(MatrixRoom, Option<Tags>)>;
|
|
|
|
|
2023-03-20 16:01:02 -07:00
|
|
|
const MEMBER_FETCH_DEBOUNCE: Duration = Duration::from_secs(5);
|
|
|
|
|
2023-01-05 18:12:25 -08:00
|
|
|
#[inline]
|
|
|
|
fn bold_style() -> Style {
|
|
|
|
Style::default().add_modifier(StyleModifier::BOLD)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn bold_span(s: &str) -> Span {
|
|
|
|
Span::styled(s, bold_style())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2023-07-01 08:58:48 +01:00
|
|
|
fn bold_spans(s: &str) -> Line {
|
2023-01-05 18:12:25 -08:00
|
|
|
bold_span(s).into()
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
#[inline]
|
|
|
|
fn selected_style(selected: bool) -> Style {
|
|
|
|
if selected {
|
|
|
|
Style::default().add_modifier(StyleModifier::REVERSED)
|
|
|
|
} else {
|
|
|
|
Style::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn selected_span(s: &str, selected: bool) -> Span {
|
|
|
|
Span::styled(s, selected_style(selected))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn selected_text(s: &str, selected: bool) -> Text {
|
|
|
|
Text::from(selected_span(s, selected))
|
|
|
|
}
|
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
/// Sort `Some` to be less than `None` so that list items with values come before those without.
|
|
|
|
#[inline]
|
2024-02-28 09:03:28 -08:00
|
|
|
fn some_cmp<T, F>(a: Option<T>, b: Option<T>, f: F) -> Ordering
|
|
|
|
where
|
|
|
|
F: Fn(&T, &T) -> Ordering,
|
|
|
|
{
|
2023-10-20 19:32:33 -07:00
|
|
|
match (a, b) {
|
2024-02-28 09:03:28 -08:00
|
|
|
(Some(a), Some(b)) => f(&a, &b),
|
2023-01-10 19:59:30 -08:00
|
|
|
(None, None) => Ordering::Equal,
|
|
|
|
(None, Some(_)) => Ordering::Greater,
|
|
|
|
(Some(_), None) => Ordering::Less,
|
2023-10-20 19:32:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn user_cmp(a: &MemberItem, b: &MemberItem, field: &SortFieldUser) -> Ordering {
|
|
|
|
let a_id = a.member.user_id();
|
|
|
|
let b_id = b.member.user_id();
|
2023-01-10 19:59:30 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
match field {
|
|
|
|
SortFieldUser::UserId => a_id.cmp(b_id),
|
|
|
|
SortFieldUser::LocalPart => a_id.localpart().cmp(b_id.localpart()),
|
|
|
|
SortFieldUser::Server => a_id.server_name().cmp(b_id.server_name()),
|
|
|
|
SortFieldUser::PowerLevel => {
|
|
|
|
// Sort higher power levels towards the top of the list.
|
|
|
|
b.member.power_level().cmp(&a.member.power_level())
|
|
|
|
},
|
|
|
|
}
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
fn room_cmp<T: RoomLikeItem>(a: &T, b: &T, field: &SortFieldRoom) -> Ordering {
|
|
|
|
match field {
|
|
|
|
SortFieldRoom::Favorite => {
|
|
|
|
let fava = a.has_tag(TagName::Favorite);
|
|
|
|
let favb = b.has_tag(TagName::Favorite);
|
2023-01-26 15:23:15 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
// If a has Favorite and b doesn't, it should sort earlier in room list.
|
|
|
|
favb.cmp(&fava)
|
|
|
|
},
|
|
|
|
SortFieldRoom::LowPriority => {
|
|
|
|
let lowa = a.has_tag(TagName::LowPriority);
|
|
|
|
let lowb = b.has_tag(TagName::LowPriority);
|
2023-01-26 15:23:15 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
// If a has LowPriority and b doesn't, it should sort later in room list.
|
|
|
|
lowa.cmp(&lowb)
|
|
|
|
},
|
|
|
|
SortFieldRoom::Name => a.name().cmp(b.name()),
|
2024-02-28 09:03:28 -08:00
|
|
|
SortFieldRoom::Alias => some_cmp(a.alias(), b.alias(), Ord::cmp),
|
2023-10-20 19:32:33 -07:00
|
|
|
SortFieldRoom::RoomId => a.room_id().cmp(b.room_id()),
|
2024-02-28 09:03:28 -08:00
|
|
|
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))
|
|
|
|
},
|
2025-05-15 04:39:22 +02:00
|
|
|
SortFieldRoom::Invite => {
|
|
|
|
// sort invites before other rooms.
|
|
|
|
b.is_invite().cmp(&a.is_invite())
|
|
|
|
},
|
2023-10-20 19:32:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Compare two rooms according the configured sort criteria.
|
|
|
|
fn room_fields_cmp<T: RoomLikeItem>(
|
|
|
|
a: &T,
|
|
|
|
b: &T,
|
|
|
|
fields: &[SortColumn<SortFieldRoom>],
|
|
|
|
) -> Ordering {
|
|
|
|
for SortColumn(field, order) in fields {
|
|
|
|
match (room_cmp(a, b, field), order) {
|
|
|
|
(Ordering::Equal, _) => continue,
|
|
|
|
(o, SortOrder::Ascending) => return o,
|
|
|
|
(o, SortOrder::Descending) => return o.reverse(),
|
|
|
|
}
|
|
|
|
}
|
2023-01-26 15:23:15 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
// Break ties on ascending room id.
|
|
|
|
room_cmp(a, b, &SortFieldRoom::RoomId)
|
|
|
|
}
|
2023-01-26 15:23:15 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
fn user_fields_cmp(
|
|
|
|
a: &MemberItem,
|
|
|
|
b: &MemberItem,
|
|
|
|
fields: &[SortColumn<SortFieldUser>],
|
|
|
|
) -> Ordering {
|
|
|
|
for SortColumn(field, order) in fields {
|
|
|
|
match (user_cmp(a, b, field), order) {
|
|
|
|
(Ordering::Equal, _) => continue,
|
|
|
|
(o, SortOrder::Ascending) => return o,
|
|
|
|
(o, SortOrder::Descending) => return o.reverse(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Break ties on ascending user id.
|
|
|
|
user_cmp(a, b, &SortFieldUser::UserId)
|
2023-01-26 15:23:15 -08:00
|
|
|
}
|
|
|
|
|
2024-02-27 18:36:09 -08:00
|
|
|
fn tag_to_span(tag: &TagName, style: Style) -> Vec<Span<'_>> {
|
|
|
|
match tag {
|
|
|
|
TagName::Favorite => vec![Span::styled("Favorite", style)],
|
|
|
|
TagName::LowPriority => vec![Span::styled("Low Priority", style)],
|
|
|
|
TagName::ServerNotice => vec![Span::styled("Server Notice", style)],
|
|
|
|
TagName::User(tag) => {
|
|
|
|
vec![
|
|
|
|
Span::styled("User Tag: ", style),
|
|
|
|
Span::styled(tag.as_ref(), style),
|
|
|
|
]
|
|
|
|
},
|
|
|
|
tag => vec![Span::styled(format!("{tag:?}"), style)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn append_tags<'a>(tags: Vec<Vec<Span<'a>>>, spans: &mut Vec<Span<'a>>, style: Style) {
|
2023-01-26 15:23:15 -08:00
|
|
|
if tags.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
spans.push(Span::styled(" (", style));
|
|
|
|
|
2024-02-27 18:36:09 -08:00
|
|
|
for (i, tag) in tags.into_iter().enumerate() {
|
2023-01-26 15:23:15 -08:00
|
|
|
if i > 0 {
|
|
|
|
spans.push(Span::styled(", ", style));
|
|
|
|
}
|
|
|
|
|
2024-02-27 18:36:09 -08:00
|
|
|
spans.extend(tag);
|
2023-01-26 15:23:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
spans.push(Span::styled(")", style));
|
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
trait RoomLikeItem {
|
|
|
|
fn room_id(&self) -> &RoomId;
|
|
|
|
fn has_tag(&self, tag: TagName) -> bool;
|
2024-02-28 09:03:28 -08:00
|
|
|
fn is_unread(&self) -> bool;
|
|
|
|
fn recent_ts(&self) -> Option<&MessageTimeStamp>;
|
2023-10-20 19:32:33 -07:00
|
|
|
fn alias(&self) -> Option<&RoomAliasId>;
|
|
|
|
fn name(&self) -> &str;
|
2025-05-15 04:39:22 +02:00
|
|
|
fn is_invite(&self) -> bool;
|
2023-10-20 19:32:33 -07:00
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
#[inline]
|
|
|
|
fn room_prompt(
|
|
|
|
room_id: &RoomId,
|
|
|
|
act: &PromptAction,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
|
|
|
match act {
|
|
|
|
PromptAction::Submit => {
|
2024-03-09 00:47:05 -08:00
|
|
|
let room = IambId::Room(room_id.to_owned(), None);
|
2022-12-29 18:00:59 -08:00
|
|
|
let open = WindowAction::Switch(OpenTarget::Application(room));
|
|
|
|
let acts = vec![(open.into(), ctx.clone())];
|
|
|
|
|
|
|
|
Ok(acts)
|
|
|
|
},
|
|
|
|
PromptAction::Abort(_) => {
|
|
|
|
let msg = "Cannot abort entry inside a list";
|
|
|
|
let err = EditError::Failure(msg.into());
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
},
|
2023-04-28 16:52:33 -07:00
|
|
|
PromptAction::Recall(..) => {
|
2022-12-29 18:00:59 -08:00
|
|
|
let msg = "Cannot recall history inside a list";
|
|
|
|
let err = EditError::Failure(msg.into());
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! delegate {
|
|
|
|
($s: expr, $id: ident => $e: expr) => {
|
|
|
|
match $s {
|
|
|
|
IambWindow::Room($id) => $e,
|
|
|
|
IambWindow::DirectList($id) => $e,
|
2023-03-20 16:01:02 -07:00
|
|
|
IambWindow::MemberList($id, _, _) => $e,
|
2022-12-29 18:00:59 -08:00
|
|
|
IambWindow::RoomList($id) => $e,
|
|
|
|
IambWindow::SpaceList($id) => $e,
|
|
|
|
IambWindow::VerifyList($id) => $e,
|
|
|
|
IambWindow::Welcome($id) => $e,
|
2024-02-27 18:36:09 -08:00
|
|
|
IambWindow::ChatList($id) => $e,
|
2024-08-20 19:33:46 -07:00
|
|
|
IambWindow::UnreadList($id) => $e,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum IambWindow {
|
|
|
|
DirectList(DirectListState),
|
2023-03-20 16:01:02 -07:00
|
|
|
MemberList(MemberListState, OwnedRoomId, Option<Instant>),
|
2022-12-29 18:00:59 -08:00
|
|
|
Room(RoomState),
|
|
|
|
VerifyList(VerifyListState),
|
|
|
|
RoomList(RoomListState),
|
|
|
|
SpaceList(SpaceListState),
|
|
|
|
Welcome(WelcomeState),
|
2024-02-27 18:36:09 -08:00
|
|
|
ChatList(ChatListState),
|
2024-08-20 19:33:46 -07:00
|
|
|
UnreadList(UnreadListState),
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl IambWindow {
|
|
|
|
pub fn focus_toggle(&mut self) {
|
|
|
|
if let IambWindow::Room(w) = self {
|
|
|
|
w.focus_toggle()
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
pub async fn message_command(
|
|
|
|
&mut self,
|
|
|
|
act: MessageAction,
|
|
|
|
ctx: ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<EditInfo> {
|
|
|
|
if let IambWindow::Room(w) = self {
|
|
|
|
w.message_command(act, ctx, store).await
|
|
|
|
} else {
|
|
|
|
return Err(IambError::NoSelectedRoom.into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-15 03:26:35 +00:00
|
|
|
pub async fn space_command(
|
|
|
|
&mut self,
|
|
|
|
act: SpaceAction,
|
|
|
|
ctx: ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<EditInfo> {
|
|
|
|
if let IambWindow::Room(w) = self {
|
|
|
|
w.space_command(act, ctx, store).await
|
|
|
|
} else {
|
|
|
|
return Err(IambError::NoSelectedRoom.into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
pub async fn room_command(
|
2023-01-04 12:51:33 -08:00
|
|
|
&mut self,
|
|
|
|
act: RoomAction,
|
|
|
|
ctx: ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
|
|
|
|
if let IambWindow::Room(w) = self {
|
2023-01-10 19:59:30 -08:00
|
|
|
w.room_command(act, ctx, store).await
|
2023-01-04 12:51:33 -08:00
|
|
|
} else {
|
2023-01-10 19:59:30 -08:00
|
|
|
return Err(IambError::NoSelectedRoomOrSpace.into());
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 12:51:33 -08:00
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
pub async fn send_command(
|
|
|
|
&mut self,
|
|
|
|
act: SendAction,
|
|
|
|
ctx: ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<EditInfo> {
|
|
|
|
if let IambWindow::Room(w) = self {
|
|
|
|
w.send_command(act, ctx, store).await
|
|
|
|
} else {
|
|
|
|
return Err(IambError::NoSelectedRoom.into());
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub type DirectListState = ListState<DirectItem, IambInfo>;
|
2023-01-04 12:51:33 -08:00
|
|
|
pub type MemberListState = ListState<MemberItem, IambInfo>;
|
2022-12-29 18:00:59 -08:00
|
|
|
pub type RoomListState = ListState<RoomItem, IambInfo>;
|
2024-02-27 18:36:09 -08:00
|
|
|
pub type ChatListState = ListState<GenericChatItem, IambInfo>;
|
2024-08-20 19:33:46 -07:00
|
|
|
pub type UnreadListState = ListState<GenericChatItem, IambInfo>;
|
2022-12-29 18:00:59 -08:00
|
|
|
pub type SpaceListState = ListState<SpaceItem, IambInfo>;
|
|
|
|
pub type VerifyListState = ListState<VerifyItem, IambInfo>;
|
|
|
|
|
2024-02-27 18:36:09 -08:00
|
|
|
impl From<ChatListState> for IambWindow {
|
|
|
|
fn from(list: ChatListState) -> Self {
|
|
|
|
IambWindow::ChatList(list)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
impl From<RoomState> for IambWindow {
|
|
|
|
fn from(room: RoomState) -> Self {
|
|
|
|
IambWindow::Room(room)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<VerifyListState> for IambWindow {
|
|
|
|
fn from(list: VerifyListState) -> Self {
|
|
|
|
IambWindow::VerifyList(list)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<DirectListState> for IambWindow {
|
|
|
|
fn from(list: DirectListState) -> Self {
|
|
|
|
IambWindow::DirectList(list)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<RoomListState> for IambWindow {
|
|
|
|
fn from(list: RoomListState) -> Self {
|
|
|
|
IambWindow::RoomList(list)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<SpaceListState> for IambWindow {
|
|
|
|
fn from(list: SpaceListState) -> Self {
|
|
|
|
IambWindow::SpaceList(list)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<WelcomeState> for IambWindow {
|
|
|
|
fn from(win: WelcomeState) -> Self {
|
|
|
|
IambWindow::Welcome(win)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Editable<ProgramContext, ProgramStore, IambInfo> for IambWindow {
|
|
|
|
fn editor_command(
|
|
|
|
&mut self,
|
|
|
|
act: &EditorAction,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> EditResult<EditInfo, IambInfo> {
|
|
|
|
delegate!(self, w => w.editor_command(act, ctx, store))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Jumpable<ProgramContext, IambInfo> for IambWindow {
|
|
|
|
fn jump(
|
|
|
|
&mut self,
|
|
|
|
list: PositionList,
|
|
|
|
dir: MoveDir1D,
|
|
|
|
count: usize,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
) -> IambResult<usize> {
|
|
|
|
delegate!(self, w => w.jump(list, dir, count, ctx))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Scrollable<ProgramContext, ProgramStore, IambInfo> for IambWindow {
|
|
|
|
fn scroll(
|
|
|
|
&mut self,
|
|
|
|
style: &ScrollStyle,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> EditResult<EditInfo, IambInfo> {
|
|
|
|
delegate!(self, w => w.scroll(style, ctx, store))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for IambWindow {
|
|
|
|
fn prompt(
|
|
|
|
&mut self,
|
|
|
|
act: &PromptAction,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
|
|
|
delegate!(self, w => w.prompt(act, ctx, store))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TerminalCursor for IambWindow {
|
|
|
|
fn get_term_cursor(&self) -> Option<TermOffset> {
|
|
|
|
delegate!(self, w => w.get_term_cursor())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WindowOps<IambInfo> for IambWindow {
|
|
|
|
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
|
|
|
|
match self {
|
2023-01-05 18:12:25 -08:00
|
|
|
IambWindow::Room(state) => state.draw(area, buf, focused, store),
|
2022-12-29 18:00:59 -08:00
|
|
|
IambWindow::DirectList(state) => {
|
2023-07-05 15:25:42 -07:00
|
|
|
let mut items = store
|
|
|
|
.application
|
|
|
|
.sync_info
|
|
|
|
.dms
|
|
|
|
.clone()
|
2023-01-26 15:23:15 -08:00
|
|
|
.into_iter()
|
2023-07-05 15:25:42 -07:00
|
|
|
.map(|room_info| DirectItem::new(room_info, store))
|
2023-01-26 15:23:15 -08:00
|
|
|
.collect::<Vec<_>>();
|
2023-10-20 19:32:33 -07:00
|
|
|
let fields = &store.application.settings.tunables.sort.dms;
|
|
|
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
2023-01-26 15:23:15 -08:00
|
|
|
|
|
|
|
state.set(items);
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
List::new(store)
|
|
|
|
.empty_message("No direct messages yet!")
|
|
|
|
.empty_alignment(Alignment::Center)
|
|
|
|
.focus(focused)
|
2023-01-05 18:12:25 -08:00
|
|
|
.render(area, buf, state);
|
2023-01-04 12:51:33 -08:00
|
|
|
},
|
2023-03-20 16:01:02 -07:00
|
|
|
IambWindow::MemberList(state, room_id, last_fetch) => {
|
|
|
|
let need_fetch = match last_fetch {
|
|
|
|
Some(i) => i.elapsed() >= MEMBER_FETCH_DEBOUNCE,
|
|
|
|
None => true,
|
|
|
|
};
|
|
|
|
|
|
|
|
if need_fetch {
|
|
|
|
if let Ok(mems) = store.application.worker.members(room_id.clone()) {
|
2023-10-20 19:32:33 -07:00
|
|
|
let mut items = mems
|
|
|
|
.into_iter()
|
|
|
|
.map(|m| MemberItem::new(m, room_id.clone()))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
let fields = &store.application.settings.tunables.sort.members;
|
|
|
|
items.sort_by(|a, b| user_fields_cmp(a, b, fields));
|
|
|
|
state.set(items);
|
2023-03-20 16:01:02 -07:00
|
|
|
*last_fetch = Some(Instant::now());
|
|
|
|
}
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
List::new(store)
|
|
|
|
.empty_message("No users here yet!")
|
|
|
|
.empty_alignment(Alignment::Center)
|
|
|
|
.focus(focused)
|
2023-01-05 18:12:25 -08:00
|
|
|
.render(area, buf, state);
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
|
|
|
IambWindow::RoomList(state) => {
|
2023-07-05 15:25:42 -07:00
|
|
|
let mut items = store
|
|
|
|
.application
|
|
|
|
.sync_info
|
|
|
|
.rooms
|
|
|
|
.clone()
|
2023-01-10 19:59:30 -08:00
|
|
|
.into_iter()
|
2023-07-05 15:25:42 -07:00
|
|
|
.map(|room_info| RoomItem::new(room_info, store))
|
2023-01-10 19:59:30 -08:00
|
|
|
.collect::<Vec<_>>();
|
2023-10-20 19:32:33 -07:00
|
|
|
let fields = &store.application.settings.tunables.sort.rooms;
|
|
|
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
2023-01-10 19:59:30 -08:00
|
|
|
|
|
|
|
state.set(items);
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
List::new(store)
|
|
|
|
.empty_message("You haven't joined any rooms yet")
|
|
|
|
.empty_alignment(Alignment::Center)
|
|
|
|
.focus(focused)
|
2023-01-05 18:12:25 -08:00
|
|
|
.render(area, buf, state);
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
2024-02-27 18:36:09 -08:00
|
|
|
IambWindow::ChatList(state) => {
|
|
|
|
let mut items = store
|
|
|
|
.application
|
|
|
|
.sync_info
|
|
|
|
.rooms
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.map(|room_info| GenericChatItem::new(room_info, store, false))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let dms = store
|
|
|
|
.application
|
|
|
|
.sync_info
|
|
|
|
.dms
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.map(|room_info| GenericChatItem::new(room_info, store, true));
|
|
|
|
|
|
|
|
items.extend(dms);
|
|
|
|
|
|
|
|
let fields = &store.application.settings.tunables.sort.chats;
|
|
|
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
|
|
|
|
state.set(items);
|
|
|
|
|
|
|
|
List::new(store)
|
|
|
|
.empty_message("You do not have rooms or dms yet")
|
|
|
|
.empty_alignment(Alignment::Center)
|
|
|
|
.focus(focused)
|
|
|
|
.render(area, buf, state);
|
|
|
|
},
|
2024-08-20 19:33:46 -07:00
|
|
|
IambWindow::UnreadList(state) => {
|
|
|
|
let mut items = store
|
|
|
|
.application
|
|
|
|
.sync_info
|
|
|
|
.rooms
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.map(|room_info| GenericChatItem::new(room_info, store, false))
|
|
|
|
.filter(RoomLikeItem::is_unread)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let dms = store
|
|
|
|
.application
|
|
|
|
.sync_info
|
|
|
|
.dms
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
|
|
|
.map(|room_info| GenericChatItem::new(room_info, store, true))
|
|
|
|
.filter(RoomLikeItem::is_unread);
|
|
|
|
|
|
|
|
items.extend(dms);
|
|
|
|
|
|
|
|
let fields = &store.application.settings.tunables.sort.chats;
|
|
|
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
|
|
|
|
state.set(items);
|
|
|
|
|
|
|
|
List::new(store)
|
|
|
|
.empty_message("You do not have rooms or dms yet")
|
|
|
|
.empty_alignment(Alignment::Center)
|
|
|
|
.focus(focused)
|
|
|
|
.render(area, buf, state);
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
IambWindow::SpaceList(state) => {
|
2023-10-20 19:32:33 -07:00
|
|
|
let mut items = store
|
2023-07-05 15:25:42 -07:00
|
|
|
.application
|
|
|
|
.sync_info
|
|
|
|
.spaces
|
|
|
|
.clone()
|
|
|
|
.into_iter()
|
2023-10-20 19:32:33 -07:00
|
|
|
.map(|room| SpaceItem::new(room, store))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
let fields = &store.application.settings.tunables.sort.spaces;
|
|
|
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
|
|
|
|
state.set(items);
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
List::new(store)
|
|
|
|
.empty_message("You haven't joined any spaces yet")
|
|
|
|
.empty_alignment(Alignment::Center)
|
|
|
|
.focus(focused)
|
2023-01-05 18:12:25 -08:00
|
|
|
.render(area, buf, state);
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
|
|
|
IambWindow::VerifyList(state) => {
|
|
|
|
let verifications = &store.application.verifications;
|
|
|
|
let mut items = verifications.iter().map(VerifyItem::from).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
// Sort the active verifications towards the top.
|
|
|
|
items.sort();
|
|
|
|
|
|
|
|
state.set(items);
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
List::new(store)
|
|
|
|
.empty_message("No in-progress verifications")
|
|
|
|
.empty_alignment(Alignment::Center)
|
|
|
|
.focus(focused)
|
2023-01-05 18:12:25 -08:00
|
|
|
.render(area, buf, state);
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
2023-01-05 18:12:25 -08:00
|
|
|
IambWindow::Welcome(state) => state.draw(area, buf, focused, store),
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn dup(&self, store: &mut ProgramStore) -> Self {
|
2023-01-04 12:51:33 -08:00
|
|
|
match self {
|
|
|
|
IambWindow::Room(w) => w.dup(store).into(),
|
|
|
|
IambWindow::DirectList(w) => w.dup(store).into(),
|
2023-03-20 16:01:02 -07:00
|
|
|
IambWindow::MemberList(w, room_id, last_fetch) => {
|
|
|
|
IambWindow::MemberList(w.dup(store), room_id.clone(), *last_fetch)
|
2023-01-04 12:51:33 -08:00
|
|
|
},
|
|
|
|
IambWindow::RoomList(w) => w.dup(store).into(),
|
|
|
|
IambWindow::SpaceList(w) => w.dup(store).into(),
|
|
|
|
IambWindow::VerifyList(w) => w.dup(store).into(),
|
|
|
|
IambWindow::Welcome(w) => w.dup(store).into(),
|
2024-02-27 18:36:09 -08:00
|
|
|
IambWindow::ChatList(w) => w.dup(store).into(),
|
2024-08-20 19:33:46 -07:00
|
|
|
IambWindow::UnreadList(w) => w.dup(store).into(),
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn close(&mut self, flags: CloseFlags, store: &mut ProgramStore) -> bool {
|
|
|
|
delegate!(self, w => w.close(flags, store))
|
|
|
|
}
|
|
|
|
|
2023-03-01 18:46:33 -08:00
|
|
|
fn write(
|
|
|
|
&mut self,
|
|
|
|
path: Option<&str>,
|
|
|
|
flags: WriteFlags,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> IambResult<EditInfo> {
|
|
|
|
delegate!(self, w => w.write(path, flags, store))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_completions(&self) -> Option<CompletionList> {
|
|
|
|
delegate!(self, w => w.get_completions())
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
fn get_cursor_word(&self, style: &WordStyle) -> Option<String> {
|
|
|
|
delegate!(self, w => w.get_cursor_word(style))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_selected_word(&self) -> Option<String> {
|
|
|
|
delegate!(self, w => w.get_selected_word())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Window<IambInfo> for IambWindow {
|
|
|
|
fn id(&self) -> IambId {
|
|
|
|
match self {
|
2024-03-09 00:47:05 -08:00
|
|
|
IambWindow::Room(room) => IambId::Room(room.id().to_owned(), room.thread().cloned()),
|
2022-12-29 18:00:59 -08:00
|
|
|
IambWindow::DirectList(_) => IambId::DirectList,
|
2023-03-20 16:01:02 -07:00
|
|
|
IambWindow::MemberList(_, room_id, _) => IambId::MemberList(room_id.clone()),
|
2022-12-29 18:00:59 -08:00
|
|
|
IambWindow::RoomList(_) => IambId::RoomList,
|
|
|
|
IambWindow::SpaceList(_) => IambId::SpaceList,
|
|
|
|
IambWindow::VerifyList(_) => IambId::VerifyList,
|
|
|
|
IambWindow::Welcome(_) => IambId::Welcome,
|
2024-02-27 18:36:09 -08:00
|
|
|
IambWindow::ChatList(_) => IambId::ChatList,
|
2024-08-20 19:33:46 -07:00
|
|
|
IambWindow::UnreadList(_) => IambId::UnreadList,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-01 08:58:48 +01:00
|
|
|
fn get_tab_title(&self, store: &mut ProgramStore) -> Line {
|
2023-01-05 18:12:25 -08:00
|
|
|
match self {
|
|
|
|
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
|
|
|
|
IambWindow::RoomList(_) => bold_spans("Rooms"),
|
|
|
|
IambWindow::SpaceList(_) => bold_spans("Spaces"),
|
|
|
|
IambWindow::VerifyList(_) => bold_spans("Verifications"),
|
|
|
|
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
|
2024-02-27 18:36:09 -08:00
|
|
|
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
|
2024-08-20 19:33:46 -07:00
|
|
|
IambWindow::UnreadList(_) => bold_spans("Unread Messages"),
|
2023-01-05 18:12:25 -08:00
|
|
|
|
|
|
|
IambWindow::Room(w) => {
|
|
|
|
let title = store.application.get_room_title(w.id());
|
|
|
|
|
2023-07-01 08:58:48 +01:00
|
|
|
Line::from(title)
|
2023-01-05 18:12:25 -08:00
|
|
|
},
|
2023-06-14 19:31:43 -07:00
|
|
|
IambWindow::MemberList(state, room_id, _) => {
|
2023-01-05 18:12:25 -08:00
|
|
|
let title = store.application.get_room_title(room_id.as_ref());
|
2023-06-14 19:31:43 -07:00
|
|
|
let n = state.len();
|
|
|
|
let v = vec![
|
|
|
|
bold_span("Room Members "),
|
|
|
|
Span::styled(format!("({n}): "), bold_style()),
|
|
|
|
title.into(),
|
|
|
|
];
|
2023-07-01 08:58:48 +01:00
|
|
|
Line::from(v)
|
2023-06-14 20:28:01 -07:00
|
|
|
},
|
2023-01-05 18:12:25 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-01 08:58:48 +01:00
|
|
|
fn get_win_title(&self, store: &mut ProgramStore) -> Line {
|
2023-01-05 18:12:25 -08:00
|
|
|
match self {
|
|
|
|
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
|
|
|
|
IambWindow::RoomList(_) => bold_spans("Rooms"),
|
|
|
|
IambWindow::SpaceList(_) => bold_spans("Spaces"),
|
|
|
|
IambWindow::VerifyList(_) => bold_spans("Verifications"),
|
|
|
|
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
|
2024-02-27 18:36:09 -08:00
|
|
|
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
|
2024-08-20 19:33:46 -07:00
|
|
|
IambWindow::UnreadList(_) => bold_spans("Unread Messages"),
|
2023-01-05 18:12:25 -08:00
|
|
|
|
|
|
|
IambWindow::Room(w) => w.get_title(store),
|
2023-06-14 19:31:43 -07:00
|
|
|
IambWindow::MemberList(state, room_id, _) => {
|
2023-01-05 18:12:25 -08:00
|
|
|
let title = store.application.get_room_title(room_id.as_ref());
|
2023-06-14 19:31:43 -07:00
|
|
|
let n = state.len();
|
|
|
|
let v = vec![
|
|
|
|
bold_span("Room Members "),
|
|
|
|
Span::styled(format!("({n}): "), bold_style()),
|
|
|
|
title.into(),
|
|
|
|
];
|
2023-07-01 08:58:48 +01:00
|
|
|
Line::from(v)
|
2023-01-05 18:12:25 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
fn open(id: IambId, store: &mut ProgramStore) -> IambResult<Self> {
|
|
|
|
match id {
|
2024-03-09 00:47:05 -08:00
|
|
|
IambId::Room(room_id, thread) => {
|
2023-01-25 17:54:16 -08:00
|
|
|
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
2024-03-09 00:47:05 -08:00
|
|
|
let room = RoomState::new(room, thread, name, tags, store);
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
|
2022-12-29 18:00:59 -08:00
|
|
|
return Ok(room.into());
|
|
|
|
},
|
|
|
|
IambId::DirectList => {
|
|
|
|
let list = DirectListState::new(IambBufferId::DirectList, vec![]);
|
|
|
|
|
|
|
|
return Ok(list.into());
|
|
|
|
},
|
2023-01-04 12:51:33 -08:00
|
|
|
IambId::MemberList(room_id) => {
|
|
|
|
let id = IambBufferId::MemberList(room_id.clone());
|
|
|
|
let list = MemberListState::new(id, vec![]);
|
2023-03-20 16:01:02 -07:00
|
|
|
let win = IambWindow::MemberList(list, room_id, None);
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
return Ok(win);
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
IambId::RoomList => {
|
|
|
|
let list = RoomListState::new(IambBufferId::RoomList, vec![]);
|
|
|
|
|
|
|
|
return Ok(list.into());
|
|
|
|
},
|
|
|
|
IambId::SpaceList => {
|
|
|
|
let list = SpaceListState::new(IambBufferId::SpaceList, vec![]);
|
|
|
|
|
|
|
|
return Ok(list.into());
|
|
|
|
},
|
|
|
|
IambId::VerifyList => {
|
|
|
|
let list = VerifyListState::new(IambBufferId::VerifyList, vec![]);
|
|
|
|
|
|
|
|
return Ok(list.into());
|
|
|
|
},
|
|
|
|
IambId::Welcome => {
|
|
|
|
let win = WelcomeState::new(store);
|
|
|
|
|
|
|
|
return Ok(win.into());
|
|
|
|
},
|
2024-02-27 18:36:09 -08:00
|
|
|
IambId::ChatList => {
|
|
|
|
let list = ChatListState::new(IambBufferId::ChatList, vec![]);
|
|
|
|
|
|
|
|
Ok(list.into())
|
|
|
|
},
|
2024-08-20 19:33:46 -07:00
|
|
|
IambId::UnreadList => {
|
|
|
|
let list = UnreadListState::new(IambBufferId::UnreadList, vec![]);
|
|
|
|
|
|
|
|
Ok(IambWindow::UnreadList(list))
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find(name: String, store: &mut ProgramStore) -> IambResult<Self> {
|
|
|
|
let ChatStore { names, worker, .. } = &mut store.application;
|
|
|
|
|
2023-03-01 18:46:33 -08:00
|
|
|
if let Some(room) = names.get_mut(&name) {
|
2024-03-09 00:47:05 -08:00
|
|
|
let id = IambId::Room(room.clone(), None);
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-03-01 18:46:33 -08:00
|
|
|
IambWindow::open(id, store)
|
|
|
|
} else {
|
|
|
|
let room_id = worker.join_room(name.clone())?;
|
|
|
|
names.insert(name, room_id.clone());
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-03-01 18:46:33 -08:00
|
|
|
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
2024-03-09 00:47:05 -08:00
|
|
|
let room = RoomState::new(room, None, name, tags, store);
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
|
2023-03-01 18:46:33 -08:00
|
|
|
Ok(room.into())
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn posn(index: usize, _: &mut ProgramStore) -> IambResult<Self> {
|
2023-01-30 13:51:32 -08:00
|
|
|
let msg = format!("Cannot find indexed buffer (index = {index})");
|
2022-12-29 18:00:59 -08:00
|
|
|
let err = UIError::Unimplemented(msg);
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
}
|
2023-01-05 18:12:25 -08:00
|
|
|
|
|
|
|
fn unnamed(store: &mut ProgramStore) -> IambResult<Self> {
|
|
|
|
Self::open(IambId::RoomList, store)
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2024-02-27 18:36:09 -08:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct GenericChatItem {
|
|
|
|
room_info: MatrixRoomInfo,
|
|
|
|
name: String,
|
|
|
|
alias: Option<OwnedRoomAliasId>,
|
2024-02-28 09:03:28 -08:00
|
|
|
unread: UnreadInfo,
|
2024-02-27 18:36:09 -08:00
|
|
|
is_dm: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GenericChatItem {
|
|
|
|
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore, is_dm: bool) -> Self {
|
|
|
|
let room = &room_info.deref().0;
|
|
|
|
let room_id = room.room_id();
|
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
let info = store.application.rooms.get_or_default(room_id.to_owned());
|
2024-02-27 18:36:09 -08:00
|
|
|
let name = info.name.clone().unwrap_or_default();
|
|
|
|
let alias = room.canonical_alias();
|
2024-02-28 09:03:28 -08:00
|
|
|
let unread = info.unreads(&store.application.settings);
|
2024-08-01 06:02:42 +03:00
|
|
|
info.tags.clone_from(&room_info.deref().1);
|
2024-02-27 18:36:09 -08:00
|
|
|
|
|
|
|
if let Some(alias) = &alias {
|
|
|
|
store.application.names.insert(alias.to_string(), room_id.to_owned());
|
|
|
|
}
|
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
GenericChatItem { room_info, name, alias, is_dm, unread }
|
2024-02-27 18:36:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn room(&self) -> &MatrixRoom {
|
|
|
|
&self.room_info.deref().0
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn tags(&self) -> &Option<Tags> {
|
|
|
|
&self.room_info.deref().1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RoomLikeItem for GenericChatItem {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
self.name.as_str()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alias(&self) -> Option<&RoomAliasId> {
|
|
|
|
self.alias.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn room_id(&self) -> &RoomId {
|
|
|
|
self.room().room_id()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn has_tag(&self, tag: TagName) -> bool {
|
|
|
|
if let Some(tags) = &self.room_info.deref().1 {
|
|
|
|
tags.contains_key(&tag)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
|
|
|
|
self.unread.latest()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_unread(&self) -> bool {
|
|
|
|
self.unread.is_unread()
|
|
|
|
}
|
2025-05-15 04:39:22 +02:00
|
|
|
|
|
|
|
fn is_invite(&self) -> bool {
|
|
|
|
self.room().state() == MatrixRoomState::Invited
|
|
|
|
}
|
2024-02-27 18:36:09 -08:00
|
|
|
}
|
|
|
|
|
2024-08-01 06:02:42 +03:00
|
|
|
impl Display for GenericChatItem {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "{}", self.name)
|
2024-02-27 18:36:09 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ListItem<IambInfo> for GenericChatItem {
|
|
|
|
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
2024-02-28 09:03:28 -08:00
|
|
|
let unread = self.unread.is_unread();
|
2024-02-27 18:36:09 -08:00
|
|
|
let style = selected_style(selected);
|
2024-02-28 09:03:28 -08:00
|
|
|
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
|
|
|
let mut spans = vec![name];
|
|
|
|
|
|
|
|
labels.push(if self.is_dm {
|
|
|
|
vec![Span::styled("DM", style)]
|
2024-02-27 18:36:09 -08:00
|
|
|
} else {
|
2024-02-28 09:03:28 -08:00
|
|
|
vec![Span::styled("Room", style)]
|
|
|
|
});
|
2024-02-27 18:36:09 -08:00
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_word(&self) -> Option<String> {
|
|
|
|
self.room_id().to_string().into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for GenericChatItem {
|
|
|
|
fn prompt(
|
|
|
|
&mut self,
|
|
|
|
act: &PromptAction,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
_: &mut ProgramStore,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
|
|
|
room_prompt(self.room_id(), act, ctx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct RoomItem {
|
2023-07-05 15:25:42 -07:00
|
|
|
room_info: MatrixRoomInfo,
|
2022-12-29 18:00:59 -08:00
|
|
|
name: String,
|
2023-10-20 19:32:33 -07:00
|
|
|
alias: Option<OwnedRoomAliasId>,
|
2024-02-28 09:03:28 -08:00
|
|
|
unread: UnreadInfo,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RoomItem {
|
2023-07-05 15:25:42 -07:00
|
|
|
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self {
|
|
|
|
let room = &room_info.deref().0;
|
2023-03-01 18:46:33 -08:00
|
|
|
let room_id = room.room_id();
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
let info = store.application.rooms.get_or_default(room_id.to_owned());
|
2023-07-05 15:25:42 -07:00
|
|
|
let name = info.name.clone().unwrap_or_default();
|
2023-10-20 19:32:33 -07:00
|
|
|
let alias = room.canonical_alias();
|
2024-02-28 09:03:28 -08:00
|
|
|
let unread = info.unreads(&store.application.settings);
|
2024-08-01 06:02:42 +03:00
|
|
|
info.tags.clone_from(&room_info.deref().1);
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
if let Some(alias) = &alias {
|
2023-03-01 18:46:33 -08:00
|
|
|
store.application.names.insert(alias.to_string(), room_id.to_owned());
|
|
|
|
}
|
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
RoomItem { room_info, name, alias, unread }
|
2023-07-05 15:25:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn room(&self) -> &MatrixRoom {
|
|
|
|
&self.room_info.deref().0
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn tags(&self) -> &Option<Tags> {
|
|
|
|
&self.room_info.deref().1
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
impl RoomLikeItem for RoomItem {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
self.name.as_str()
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
fn alias(&self) -> Option<&RoomAliasId> {
|
|
|
|
self.alias.as_deref()
|
|
|
|
}
|
2023-01-10 19:59:30 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
fn room_id(&self) -> &RoomId {
|
|
|
|
self.room().room_id()
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
fn has_tag(&self, tag: TagName) -> bool {
|
|
|
|
if let Some(tags) = &self.room_info.deref().1 {
|
|
|
|
tags.contains_key(&tag)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
|
|
|
|
self.unread.latest()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_unread(&self) -> bool {
|
|
|
|
self.unread.is_unread()
|
|
|
|
}
|
2025-05-15 04:39:22 +02:00
|
|
|
|
|
|
|
fn is_invite(&self) -> bool {
|
|
|
|
self.room().state() == MatrixRoomState::Invited
|
|
|
|
}
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
|
|
|
|
2024-08-01 06:02:42 +03:00
|
|
|
impl Display for RoomItem {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2025-05-23 16:26:17 +00:00
|
|
|
write!(f, "{}", self.name)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ListItem<IambInfo> for RoomItem {
|
|
|
|
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
2024-02-28 09:03:28 -08:00
|
|
|
let unread = self.unread.is_unread();
|
|
|
|
let style = selected_style(selected);
|
|
|
|
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
|
|
|
let mut spans = vec![name];
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
if let Some(tags) = &self.tags() {
|
2024-02-28 09:03:28 -08:00
|
|
|
labels.extend(tags.keys().map(|t| tag_to_span(t, style)));
|
|
|
|
}
|
2023-01-25 17:54:16 -08:00
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
append_tags(labels, &mut spans, style);
|
2023-01-25 17:54:16 -08:00
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
Text::from(Line::from(spans))
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
fn get_word(&self) -> Option<String> {
|
2023-07-05 15:25:42 -07:00
|
|
|
self.room_id().to_string().into()
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for RoomItem {
|
|
|
|
fn prompt(
|
|
|
|
&mut self,
|
|
|
|
act: &PromptAction,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
_: &mut ProgramStore,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
2023-07-05 15:25:42 -07:00
|
|
|
room_prompt(self.room_id(), act, ctx)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct DirectItem {
|
2023-07-05 15:25:42 -07:00
|
|
|
room_info: MatrixRoomInfo,
|
2022-12-29 18:00:59 -08:00
|
|
|
name: String,
|
2023-10-20 19:32:33 -07:00
|
|
|
alias: Option<OwnedRoomAliasId>,
|
2024-02-28 09:03:28 -08:00
|
|
|
unread: UnreadInfo,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DirectItem {
|
2023-07-05 15:25:42 -07:00
|
|
|
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self {
|
2023-10-20 19:32:33 -07:00
|
|
|
let room_id = room_info.0.room_id().to_owned();
|
|
|
|
let alias = room_info.0.canonical_alias();
|
2023-07-05 15:25:42 -07:00
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
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);
|
2024-08-01 06:02:42 +03:00
|
|
|
info.tags.clone_from(&room_info.deref().1);
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
DirectItem { room_info, name, alias, unread }
|
2023-07-05 15:25:42 -07:00
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
#[inline]
|
|
|
|
fn room(&self) -> &MatrixRoom {
|
|
|
|
&self.room_info.deref().0
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn tags(&self) -> &Option<Tags> {
|
|
|
|
&self.room_info.deref().1
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
impl RoomLikeItem for DirectItem {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
self.name.as_str()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alias(&self) -> Option<&RoomAliasId> {
|
|
|
|
self.alias.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn has_tag(&self, tag: TagName) -> bool {
|
|
|
|
if let Some(tags) = &self.room_info.deref().1 {
|
|
|
|
tags.contains_key(&tag)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn room_id(&self) -> &RoomId {
|
|
|
|
self.room().room_id()
|
|
|
|
}
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
|
|
|
|
self.unread.latest()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_unread(&self) -> bool {
|
|
|
|
self.unread.is_unread()
|
|
|
|
}
|
2025-05-15 04:39:22 +02:00
|
|
|
|
|
|
|
fn is_invite(&self) -> bool {
|
|
|
|
self.room().state() == MatrixRoomState::Invited
|
|
|
|
}
|
2023-10-20 19:32:33 -07:00
|
|
|
}
|
|
|
|
|
2024-08-01 06:02:42 +03:00
|
|
|
impl Display for DirectItem {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, ":verify request {}", self.name)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ListItem<IambInfo> for DirectItem {
|
|
|
|
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
2024-02-28 09:03:28 -08:00
|
|
|
let unread = self.unread.is_unread();
|
|
|
|
let style = selected_style(selected);
|
|
|
|
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
|
|
|
let mut spans = vec![name];
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
if let Some(tags) = &self.tags() {
|
2024-02-28 09:03:28 -08:00
|
|
|
labels.extend(tags.keys().map(|t| tag_to_span(t, style)));
|
|
|
|
}
|
2023-01-26 15:23:15 -08:00
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
append_tags(labels, &mut spans, style);
|
2023-01-26 15:23:15 -08:00
|
|
|
|
2024-02-28 09:03:28 -08:00
|
|
|
Text::from(Line::from(spans))
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
fn get_word(&self) -> Option<String> {
|
2023-07-05 15:25:42 -07:00
|
|
|
self.room_id().to_string().into()
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for DirectItem {
|
|
|
|
fn prompt(
|
|
|
|
&mut self,
|
|
|
|
act: &PromptAction,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
_: &mut ProgramStore,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
2023-07-05 15:25:42 -07:00
|
|
|
room_prompt(self.room_id(), act, ctx)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct SpaceItem {
|
2023-10-20 19:32:33 -07:00
|
|
|
room_info: MatrixRoomInfo,
|
2022-12-29 18:00:59 -08:00
|
|
|
name: String,
|
2023-10-20 19:32:33 -07:00
|
|
|
alias: Option<OwnedRoomAliasId>,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SpaceItem {
|
2023-10-20 19:32:33 -07:00
|
|
|
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self {
|
|
|
|
let room_id = room_info.0.room_id();
|
2023-07-05 15:25:42 -07:00
|
|
|
let name = store
|
|
|
|
.application
|
|
|
|
.get_room_info(room_id.to_owned())
|
|
|
|
.name
|
|
|
|
.clone()
|
|
|
|
.unwrap_or_default();
|
2023-10-20 19:32:33 -07:00
|
|
|
let alias = room_info.0.canonical_alias();
|
2023-03-01 18:46:33 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
if let Some(alias) = &alias {
|
2023-03-01 18:46:33 -08:00
|
|
|
store.application.names.insert(alias.to_string(), room_id.to_owned());
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
SpaceItem { room_info, name, alias }
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
#[inline]
|
|
|
|
fn room(&self) -> &MatrixRoom {
|
|
|
|
&self.room_info.deref().0
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
impl RoomLikeItem for SpaceItem {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
self.name.as_str()
|
|
|
|
}
|
2023-01-10 19:59:30 -08:00
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
fn room_id(&self) -> &RoomId {
|
|
|
|
self.room().room_id()
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
|
|
|
|
2023-10-20 19:32:33 -07:00
|
|
|
fn alias(&self) -> Option<&RoomAliasId> {
|
|
|
|
self.alias.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn has_tag(&self, _: TagName) -> bool {
|
|
|
|
// I think that spaces can technically have tags, but afaik no client
|
|
|
|
// exposes them, so we'll just always return false here for now.
|
|
|
|
false
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2025-05-15 04:39:22 +02:00
|
|
|
|
|
|
|
fn is_invite(&self) -> bool {
|
|
|
|
self.room().state() == MatrixRoomState::Invited
|
|
|
|
}
|
2023-01-10 19:59:30 -08:00
|
|
|
}
|
|
|
|
|
2024-08-01 06:02:42 +03:00
|
|
|
impl Display for SpaceItem {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2025-05-23 16:26:17 +00:00
|
|
|
write!(f, "{}", self.name)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ListItem<IambInfo> for SpaceItem {
|
|
|
|
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
|
|
|
selected_text(self.name.as_str(), selected)
|
|
|
|
}
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
fn get_word(&self) -> Option<String> {
|
2023-10-20 19:32:33 -07:00
|
|
|
self.room_id().to_string().into()
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for SpaceItem {
|
|
|
|
fn prompt(
|
|
|
|
&mut self,
|
|
|
|
act: &PromptAction,
|
|
|
|
ctx: &ProgramContext,
|
|
|
|
_: &mut ProgramStore,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
2023-10-20 19:32:33 -07:00
|
|
|
room_prompt(self.room_id(), act, ctx)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct VerifyItem {
|
|
|
|
user_dev: String,
|
|
|
|
sasv1: SasVerification,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VerifyItem {
|
|
|
|
fn new(user_dev: String, sasv1: SasVerification) -> Self {
|
|
|
|
VerifyItem { user_dev, sasv1 }
|
|
|
|
}
|
|
|
|
|
|
|
|
fn show_item(&self) -> String {
|
|
|
|
let state = if self.sasv1.is_done() {
|
|
|
|
"done"
|
|
|
|
} else if self.sasv1.is_cancelled() {
|
|
|
|
"cancelled"
|
|
|
|
} else if self.sasv1.emoji().is_some() {
|
|
|
|
"accepted"
|
|
|
|
} else {
|
|
|
|
"not accepted"
|
|
|
|
};
|
|
|
|
|
|
|
|
if self.sasv1.is_self_verification() {
|
|
|
|
let device = self.sasv1.other_device();
|
|
|
|
|
|
|
|
if let Some(display_name) = device.display_name() {
|
2023-01-30 13:51:32 -08:00
|
|
|
format!("Device verification with {display_name} ({state})")
|
2022-12-29 18:00:59 -08:00
|
|
|
} else {
|
|
|
|
format!("Device verification with device {} ({})", device.device_id(), state)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
format!("User Verification with {} ({})", self.sasv1.other_user_id(), state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for VerifyItem {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.user_dev == other.user_dev
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for VerifyItem {}
|
|
|
|
|
|
|
|
impl Ord for VerifyItem {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
fn state_val(sas: &SasVerification) -> usize {
|
|
|
|
if sas.is_done() {
|
|
|
|
return 3;
|
|
|
|
} else if sas.is_cancelled() {
|
|
|
|
return 2;
|
|
|
|
} else {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn device_val(sas: &SasVerification) -> usize {
|
|
|
|
if sas.is_self_verification() {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let state1 = state_val(&self.sasv1);
|
|
|
|
let state2 = state_val(&other.sasv1);
|
|
|
|
|
|
|
|
let dev1 = device_val(&self.sasv1);
|
|
|
|
let dev2 = device_val(&other.sasv1);
|
|
|
|
|
|
|
|
let scmp = state1.cmp(&state2);
|
|
|
|
let dcmp = dev1.cmp(&dev2);
|
|
|
|
|
|
|
|
scmp.then(dcmp).then_with(|| {
|
|
|
|
let did1 = self.sasv1.other_device().device_id();
|
|
|
|
let did2 = other.sasv1.other_device().device_id();
|
|
|
|
|
|
|
|
did1.cmp(did2)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialOrd for VerifyItem {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
2024-02-27 18:36:09 -08:00
|
|
|
Some(self.cmp(other))
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<(&String, &SasVerification)> for VerifyItem {
|
|
|
|
fn from((user_dev, sasv1): (&String, &SasVerification)) -> Self {
|
|
|
|
VerifyItem::new(user_dev.clone(), sasv1.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-01 06:02:42 +03:00
|
|
|
impl Display for VerifyItem {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2022-12-29 18:00:59 -08:00
|
|
|
if self.sasv1.is_done() {
|
2024-08-01 06:02:42 +03:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.sasv1.is_cancelled() {
|
|
|
|
write!(f, ":verify request {}", self.sasv1.other_user_id())
|
2022-12-29 18:00:59 -08:00
|
|
|
} else if self.sasv1.emoji().is_some() {
|
2024-08-01 06:02:42 +03:00
|
|
|
write!(f, ":verify confirm {}", self.user_dev)
|
2022-12-29 18:00:59 -08:00
|
|
|
} else {
|
2024-08-01 06:02:42 +03:00
|
|
|
write!(f, ":verify accept {}", self.user_dev)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ListItem<IambInfo> for VerifyItem {
|
|
|
|
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
|
|
|
let mut lines = vec![];
|
|
|
|
|
|
|
|
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
|
|
|
let item = Span::styled(self.show_item(), selected_style(selected));
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(item));
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
if self.sasv1.is_done() {
|
|
|
|
// Print nothing.
|
|
|
|
} else if self.sasv1.is_cancelled() {
|
|
|
|
if let Some(info) = self.sasv1.cancel_info() {
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(format!(" Cancelled: {}", info.reason())));
|
|
|
|
lines.push(Line::from(""));
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(" You can start a new verification request with:"));
|
2022-12-29 18:00:59 -08:00
|
|
|
} else if let Some(emoji) = self.sasv1.emoji() {
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(
|
2022-12-29 18:00:59 -08:00
|
|
|
" Both devices should see the following Emoji sequence:".to_string(),
|
|
|
|
));
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(""));
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
for line in format_emojis(emoji).lines() {
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(format!(" {line}")));
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(""));
|
|
|
|
lines.push(Line::from(" If they don't match, run:"));
|
|
|
|
lines.push(Line::from(""));
|
|
|
|
lines.push(Line::from(Span::styled(
|
2022-12-29 18:00:59 -08:00
|
|
|
format!(":verify mismatch {}", self.user_dev),
|
|
|
|
bold,
|
|
|
|
)));
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(""));
|
|
|
|
lines.push(Line::from(" If everything looks right, you can confirm with:"));
|
2022-12-29 18:00:59 -08:00
|
|
|
} else {
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(" To accept this request, run:"));
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let cmd = self.to_string();
|
|
|
|
|
|
|
|
if !cmd.is_empty() {
|
2023-07-01 08:58:48 +01:00
|
|
|
lines.push(Line::from(""));
|
|
|
|
lines.push(Line::from(vec![Span::from(" "), Span::styled(cmd, bold)]));
|
|
|
|
lines.push(Line::from(""));
|
|
|
|
lines.push(Line::from(vec![
|
2022-12-29 18:00:59 -08:00
|
|
|
Span::from("You can copy the above command with "),
|
|
|
|
Span::styled("yy", bold),
|
|
|
|
Span::from(" and then execute it with "),
|
|
|
|
Span::styled("@\"", bold),
|
|
|
|
]));
|
|
|
|
}
|
|
|
|
|
2024-04-23 23:30:01 -07:00
|
|
|
Text::from(lines)
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
fn get_word(&self) -> Option<String> {
|
|
|
|
None
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for VerifyItem {
|
|
|
|
fn prompt(
|
|
|
|
&mut self,
|
|
|
|
act: &PromptAction,
|
|
|
|
_: &ProgramContext,
|
|
|
|
_: &mut ProgramStore,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
|
|
|
match act {
|
|
|
|
PromptAction::Submit => Ok(vec![]),
|
|
|
|
PromptAction::Abort(_) => {
|
|
|
|
let msg = "Cannot abort entry inside a list";
|
|
|
|
let err = EditError::Failure(msg.into());
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
},
|
2023-04-28 16:52:33 -07:00
|
|
|
PromptAction::Recall(..) => {
|
2022-12-29 18:00:59 -08:00
|
|
|
let msg = "Cannot recall history inside a list";
|
|
|
|
let err = EditError::Failure(msg.into());
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct MemberItem {
|
|
|
|
member: RoomMember,
|
2023-07-06 23:15:58 -07:00
|
|
|
room_id: OwnedRoomId,
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MemberItem {
|
2023-07-06 23:15:58 -07:00
|
|
|
fn new(member: RoomMember, room_id: OwnedRoomId) -> Self {
|
|
|
|
Self { member, room_id }
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-01 06:02:42 +03:00
|
|
|
impl Display for MemberItem {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "{}", self.member.user_id())
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ListItem<IambInfo> for MemberItem {
|
2023-01-06 16:56:28 -08:00
|
|
|
fn show(
|
|
|
|
&self,
|
|
|
|
selected: bool,
|
|
|
|
_: &ViewportContext<ListCursor>,
|
|
|
|
store: &mut ProgramStore,
|
|
|
|
) -> Text {
|
2023-07-06 23:15:58 -07:00
|
|
|
let info = store.application.rooms.get_or_default(self.room_id.clone());
|
|
|
|
let user_id = self.member.user_id();
|
|
|
|
|
|
|
|
let (color, name) = store.application.settings.get_user_overrides(self.member.user_id());
|
|
|
|
let color = color.unwrap_or_else(|| super::config::user_color(user_id.as_str()));
|
|
|
|
let mut style = super::config::user_style_from_color(color);
|
2023-01-04 12:51:33 -08:00
|
|
|
|
|
|
|
if selected {
|
2023-07-06 23:15:58 -07:00
|
|
|
style = style.add_modifier(StyleModifier::REVERSED);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut spans = vec![];
|
|
|
|
let mut parens = false;
|
|
|
|
|
|
|
|
if let Some(name) = name {
|
|
|
|
spans.push(Span::styled(name, style));
|
|
|
|
parens = true;
|
|
|
|
} else if let Some(display) = info.display_names.get(user_id) {
|
|
|
|
spans.push(Span::styled(display.clone(), style));
|
|
|
|
parens = true;
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
|
|
|
|
2023-07-06 23:15:58 -07:00
|
|
|
spans.extend(parens.then_some(Span::styled(" (", style)));
|
|
|
|
spans.push(Span::styled(user_id.as_str(), style));
|
|
|
|
spans.extend(parens.then_some(Span::styled(")", style)));
|
|
|
|
|
2023-01-04 12:51:33 -08:00
|
|
|
let state = match self.member.membership() {
|
|
|
|
MembershipState::Ban => Span::raw(" (banned)").into(),
|
|
|
|
MembershipState::Invite => Span::raw(" (invited)").into(),
|
|
|
|
MembershipState::Knock => Span::raw(" (wants to join)").into(),
|
|
|
|
MembershipState::Leave => Span::raw(" (left)").into(),
|
|
|
|
MembershipState::Join => None,
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
2023-07-06 23:15:58 -07:00
|
|
|
spans.extend(state);
|
|
|
|
|
2023-07-01 08:58:48 +01:00
|
|
|
return Line::from(spans).into();
|
2023-01-04 12:51:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_word(&self) -> Option<String> {
|
|
|
|
self.member.user_id().to_string().into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for MemberItem {
|
|
|
|
fn prompt(
|
|
|
|
&mut self,
|
|
|
|
act: &PromptAction,
|
|
|
|
_: &ProgramContext,
|
|
|
|
_: &mut ProgramStore,
|
|
|
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
|
|
|
match act {
|
|
|
|
PromptAction::Submit => Ok(vec![]),
|
|
|
|
PromptAction::Abort(_) => {
|
|
|
|
let msg = "Cannot abort entry inside a list";
|
|
|
|
let err = EditError::Failure(msg.into());
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
},
|
2023-04-28 16:52:33 -07:00
|
|
|
PromptAction::Recall(..) => {
|
2023-01-04 12:51:33 -08:00
|
|
|
let msg = "Cannot recall history inside a list";
|
|
|
|
let err = EditError::Failure(msg.into());
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-20 19:32:33 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use matrix_sdk::ruma::{room_alias_id, server_name};
|
|
|
|
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
|
|
struct TestRoomItem {
|
|
|
|
room_id: OwnedRoomId,
|
|
|
|
tags: Vec<TagName>,
|
|
|
|
alias: Option<OwnedRoomAliasId>,
|
|
|
|
name: &'static str,
|
2024-02-28 09:03:28 -08:00
|
|
|
unread: UnreadInfo,
|
2025-05-15 04:39:22 +02:00
|
|
|
invite: bool,
|
2023-10-20 19:32:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RoomLikeItem for &TestRoomItem {
|
|
|
|
fn room_id(&self) -> &RoomId {
|
|
|
|
self.room_id.as_ref()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn has_tag(&self, tag: TagName) -> bool {
|
|
|
|
self.tags.contains(&tag)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alias(&self) -> Option<&RoomAliasId> {
|
|
|
|
self.alias.as_deref()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
self.name
|
|
|
|
}
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
fn recent_ts(&self) -> Option<&MessageTimeStamp> {
|
|
|
|
self.unread.latest()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_unread(&self) -> bool {
|
|
|
|
self.unread.is_unread()
|
|
|
|
}
|
2025-05-15 04:39:22 +02:00
|
|
|
|
|
|
|
fn is_invite(&self) -> bool {
|
|
|
|
self.invite
|
|
|
|
}
|
2023-10-20 19:32:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_sort_rooms() {
|
|
|
|
let server = server_name!("example.com");
|
|
|
|
|
|
|
|
let room1 = TestRoomItem {
|
|
|
|
room_id: RoomId::new(server).to_owned(),
|
|
|
|
tags: vec![TagName::Favorite],
|
|
|
|
alias: Some(room_alias_id!("#room1:example.com").to_owned()),
|
|
|
|
name: "Z",
|
2024-02-28 09:03:28 -08:00
|
|
|
unread: UnreadInfo::default(),
|
2025-05-15 04:39:22 +02:00
|
|
|
invite: false,
|
2023-10-20 19:32:33 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let room2 = TestRoomItem {
|
|
|
|
room_id: RoomId::new(server).to_owned(),
|
|
|
|
tags: vec![],
|
|
|
|
alias: Some(room_alias_id!("#a:example.com").to_owned()),
|
|
|
|
name: "Unnamed Room",
|
2024-02-28 09:03:28 -08:00
|
|
|
unread: UnreadInfo::default(),
|
2025-05-15 04:39:22 +02:00
|
|
|
invite: false,
|
2023-10-20 19:32:33 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let room3 = TestRoomItem {
|
|
|
|
room_id: RoomId::new(server).to_owned(),
|
|
|
|
tags: vec![],
|
|
|
|
alias: None,
|
|
|
|
name: "Cool Room",
|
2024-02-28 09:03:28 -08:00
|
|
|
unread: UnreadInfo::default(),
|
2025-05-15 04:39:22 +02:00
|
|
|
invite: false,
|
2023-10-20 19:32:33 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// Sort by Name ascending.
|
|
|
|
let mut rooms = vec![&room1, &room2, &room3];
|
|
|
|
let fields = &[SortColumn(SortFieldRoom::Name, SortOrder::Ascending)];
|
|
|
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
assert_eq!(rooms, vec![&room3, &room2, &room1]);
|
|
|
|
|
|
|
|
// Sort by Name descending.
|
|
|
|
let mut rooms = vec![&room1, &room2, &room3];
|
|
|
|
let fields = &[SortColumn(SortFieldRoom::Name, SortOrder::Descending)];
|
|
|
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
assert_eq!(rooms, vec![&room1, &room2, &room3]);
|
|
|
|
|
|
|
|
// Sort by Favorite and Alias before Name to show order matters.
|
|
|
|
let mut rooms = vec![&room1, &room2, &room3];
|
|
|
|
let fields = &[
|
|
|
|
SortColumn(SortFieldRoom::Favorite, SortOrder::Ascending),
|
|
|
|
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
|
|
|
|
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
|
|
|
|
];
|
|
|
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
assert_eq!(rooms, vec![&room1, &room2, &room3]);
|
|
|
|
|
|
|
|
// Now flip order of Favorite with Descending
|
|
|
|
let mut rooms = vec![&room1, &room2, &room3];
|
|
|
|
let fields = &[
|
|
|
|
SortColumn(SortFieldRoom::Favorite, SortOrder::Descending),
|
|
|
|
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
|
|
|
|
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
|
|
|
|
];
|
|
|
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
assert_eq!(rooms, vec![&room2, &room3, &room1]);
|
|
|
|
}
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
#[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 },
|
2025-05-15 04:39:22 +02:00
|
|
|
invite: false,
|
2024-02-28 09:03:28 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
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())),
|
|
|
|
},
|
2025-05-15 04:39:22 +02:00
|
|
|
invite: false,
|
2024-02-28 09:03:28 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
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())),
|
|
|
|
},
|
2025-05-15 04:39:22 +02:00
|
|
|
invite: false,
|
2024-02-28 09:03:28 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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]);
|
|
|
|
}
|
2025-05-15 04:39:22 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_sort_room_invites() {
|
|
|
|
let server = server_name!("example.com");
|
|
|
|
|
|
|
|
let room1 = TestRoomItem {
|
|
|
|
room_id: RoomId::new(server).to_owned(),
|
|
|
|
tags: vec![],
|
|
|
|
alias: None,
|
|
|
|
name: "Old room 1",
|
|
|
|
unread: UnreadInfo::default(),
|
|
|
|
invite: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
let room2 = TestRoomItem {
|
|
|
|
room_id: RoomId::new(server).to_owned(),
|
|
|
|
tags: vec![],
|
|
|
|
alias: None,
|
|
|
|
name: "Old room 2",
|
|
|
|
unread: UnreadInfo::default(),
|
|
|
|
invite: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
let room3 = TestRoomItem {
|
|
|
|
room_id: RoomId::new(server).to_owned(),
|
|
|
|
tags: vec![],
|
|
|
|
alias: None,
|
|
|
|
name: "New Fancy Room",
|
|
|
|
unread: UnreadInfo::default(),
|
|
|
|
invite: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Sort invites first
|
|
|
|
let mut rooms = vec![&room1, &room2, &room3];
|
|
|
|
let fields = &[
|
|
|
|
SortColumn(SortFieldRoom::Invite, SortOrder::Ascending),
|
|
|
|
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
|
|
|
|
];
|
|
|
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
assert_eq!(rooms, vec![&room3, &room1, &room2]);
|
|
|
|
|
|
|
|
// Sort invites after
|
|
|
|
let mut rooms = vec![&room1, &room2, &room3];
|
|
|
|
let fields = &[
|
|
|
|
SortColumn(SortFieldRoom::Invite, SortOrder::Descending),
|
|
|
|
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
|
|
|
|
];
|
|
|
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
|
|
|
assert_eq!(rooms, vec![&room1, &room2, &room3]);
|
|
|
|
}
|
2023-10-20 19:32:33 -07:00
|
|
|
}
|