Custom state handing preparation, readme
This commit is mostly to be able to revert stuff if I mess it up. Also added feature list to readme
This commit is contained in:
parent
cf7c333d45
commit
686bf2892a
@ -23,6 +23,9 @@ tracing-subscriber = { version = "0.2", features = ["parking_lot"] }
|
|||||||
|
|
||||||
[dependencies.matrix-sdk]
|
[dependencies.matrix-sdk]
|
||||||
git = "https://github.com/matrix-org/matrix-rust-sdk"
|
git = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||||
rev = "bca7f41"
|
rev = "d9e5a17"
|
||||||
default_features = false
|
default_features = false
|
||||||
features = ["encryption", "sqlite_cryptostore", "messages", "rustls-tls"]
|
features = ["encryption", "sqlite_cryptostore", "messages", "rustls-tls", "unstable-synapse-quirks"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
36
README.md
36
README.md
@ -1,3 +1,37 @@
|
|||||||
# retrix
|
# retrix
|
||||||
|
|
||||||
Tiny matrix client
|
Retrix is a lightweight matrix client built with [iced] and [matrix-rust-sdk].
|
||||||
|
|
||||||
|
The project is currently in early stages, and is decidedly not feature complete. Also note that both iced and matrix-sdk are somewhat unstable and under very rapid development, which means that there might be functionality that's broken or can't be implemented that I don't have direct influence over.
|
||||||
|
|
||||||
|
# Features
|
||||||
|
- [x] Rooms
|
||||||
|
- [x] List rooms
|
||||||
|
- [ ] Join rooms
|
||||||
|
- [ ] Explore public room list
|
||||||
|
- [ ] Create room
|
||||||
|
- [ ] Communities
|
||||||
|
- [x] Messages
|
||||||
|
- [x] Plain text
|
||||||
|
- [ ] Formatted text (waiting on iced, markdown will be shown raw)
|
||||||
|
- [ ] Stickers
|
||||||
|
- [ ] Images
|
||||||
|
- [ ] Audio
|
||||||
|
- [ ] Video
|
||||||
|
- [ ] Location
|
||||||
|
- [x] E2E Encryption
|
||||||
|
- [x] Import key export
|
||||||
|
- [x] Receiving verification start
|
||||||
|
- [ ] Receiving verification request (waiting on matrix-sdk)
|
||||||
|
- [ ] Account settings
|
||||||
|
- [ ] Device management
|
||||||
|
- [ ] Change password
|
||||||
|
- [x] Profile settings
|
||||||
|
- [x] Display name
|
||||||
|
- [ ] Avatar
|
||||||
|
|
||||||
|
## Things I (currently) don't intend to implement
|
||||||
|
- VoIP Calls
|
||||||
|
|
||||||
|
[iced]: https://github.com/hecrj/iced
|
||||||
|
[matrix-rust-sdk]: https://github.com/matrix-org/matrix-rust-sdk
|
||||||
|
206
src/ui.rs
206
src/ui.rs
@ -1,4 +1,7 @@
|
|||||||
use std::collections::{BTreeMap, HashMap};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap, VecDeque},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use iced::{
|
use iced::{
|
||||||
@ -10,13 +13,30 @@ use matrix_sdk::{
|
|||||||
events::{
|
events::{
|
||||||
key::verification::cancel::CancelCode as VerificationCancelCode,
|
key::verification::cancel::CancelCode as VerificationCancelCode,
|
||||||
room::message::MessageEventContent, AnyMessageEventContent,
|
room::message::MessageEventContent, AnyMessageEventContent,
|
||||||
AnyPossiblyRedactedSyncMessageEvent, AnySyncMessageEvent, AnyToDeviceEvent,
|
AnyPossiblyRedactedSyncMessageEvent, AnyRoomEvent, AnyStateEvent, AnySyncMessageEvent,
|
||||||
|
AnyToDeviceEvent,
|
||||||
},
|
},
|
||||||
identifiers::RoomId,
|
identifiers::{RoomAliasId, RoomId, UserId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::matrix;
|
use crate::matrix;
|
||||||
|
|
||||||
|
pub trait AnyRoomEventExt {
|
||||||
|
fn origin_server_ts(&self) -> SystemTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnyRoomEventExt for AnyRoomEvent {
|
||||||
|
fn origin_server_ts(&self) -> SystemTime {
|
||||||
|
match self {
|
||||||
|
AnyRoomEvent::Message(e) => e.origin_server_ts(),
|
||||||
|
AnyRoomEvent::State(e) => e.origin_server_ts(),
|
||||||
|
AnyRoomEvent::RedactedMessage(e) => e.origin_server_ts(),
|
||||||
|
AnyRoomEvent::RedactedState(e) => e.origin_server_ts(),
|
||||||
|
}
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// View for the login prompt
|
/// View for the login prompt
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct PromptView {
|
pub struct PromptView {
|
||||||
@ -147,6 +167,99 @@ pub enum RoomSorting {
|
|||||||
Alphabetic,
|
Alphabetic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data for en entry in the room list
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RoomEntry {
|
||||||
|
/// Cached calculated name
|
||||||
|
name: String,
|
||||||
|
/// Room topic
|
||||||
|
topic: String,
|
||||||
|
/// Canonical alias
|
||||||
|
alias: Option<RoomAliasId>,
|
||||||
|
/// Defined display name
|
||||||
|
display_name: Option<String>,
|
||||||
|
/// Person we're in a direct message with
|
||||||
|
direct: Option<UserId>,
|
||||||
|
/// Button to select the room
|
||||||
|
button: iced::button::State,
|
||||||
|
/// Most recent activity in the room
|
||||||
|
updated: std::time::SystemTime,
|
||||||
|
/// Cache of messages
|
||||||
|
messages: MessageBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoomEntry {
|
||||||
|
/// Recalculate displayname
|
||||||
|
pub fn update_display_name(&mut self, id: &RoomId) {
|
||||||
|
self.name = if let Some(ref name) = self.display_name {
|
||||||
|
name.to_owned()
|
||||||
|
} else if let Some(ref user) = self.direct {
|
||||||
|
user.to_string()
|
||||||
|
} else if let Some(ref alias) = self.alias {
|
||||||
|
alias.to_string()
|
||||||
|
} else {
|
||||||
|
id.to_string()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RoomEntry {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
name: Default::default(),
|
||||||
|
topic: String::new(),
|
||||||
|
alias: None,
|
||||||
|
display_name: None,
|
||||||
|
direct: None,
|
||||||
|
button: Default::default(),
|
||||||
|
updated: std::time::SystemTime::UNIX_EPOCH,
|
||||||
|
messages: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoomEntry {
|
||||||
|
fn update_time(&mut self) {
|
||||||
|
self.updated = self.messages.update_time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct MessageBuffer {
|
||||||
|
messages: VecDeque<AnyRoomEvent>,
|
||||||
|
/// Token for the start of the messages we have
|
||||||
|
start: String,
|
||||||
|
/// Token for the end of the messages we have
|
||||||
|
end: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBuffer {
|
||||||
|
/// Sorts the messages by send time
|
||||||
|
fn sort(&mut self) {
|
||||||
|
self.messages
|
||||||
|
.make_contiguous()
|
||||||
|
.sort_unstable_by(|a, b| a.origin_server_ts().cmp(&b.origin_server_ts()).reverse())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the send time of the most recently sent message
|
||||||
|
fn update_time(&self) -> SystemTime {
|
||||||
|
match self.messages.back() {
|
||||||
|
Some(message) => message.origin_server_ts(),
|
||||||
|
None => SystemTime::UNIX_EPOCH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Insert a message that's probably the most recent
|
||||||
|
pub fn push_back(&mut self, event: AnyRoomEvent) {
|
||||||
|
self.messages.push_back(event);
|
||||||
|
self.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_front(&mut self, event: AnyRoomEvent) {
|
||||||
|
self.messages.push_front(event);
|
||||||
|
self.sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Main view after successful login
|
/// Main view after successful login
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MainView {
|
pub struct MainView {
|
||||||
@ -165,8 +278,7 @@ pub struct MainView {
|
|||||||
sas: Option<matrix_sdk::Sas>,
|
sas: Option<matrix_sdk::Sas>,
|
||||||
/// Whether to sort rooms alphabetically or by activity
|
/// Whether to sort rooms alphabetically or by activity
|
||||||
sorting: RoomSorting,
|
sorting: RoomSorting,
|
||||||
rooms: BTreeMap<RoomId, String>,
|
rooms: BTreeMap<RoomId, RoomEntry>,
|
||||||
messages: BTreeMap<RoomId, MessageEventContent>,
|
|
||||||
|
|
||||||
/// Room list entry/button to select room
|
/// Room list entry/button to select room
|
||||||
buttons: HashMap<RoomId, iced::button::State>,
|
buttons: HashMap<RoomId, iced::button::State>,
|
||||||
@ -201,10 +313,9 @@ impl MainView {
|
|||||||
message_scroll: Default::default(),
|
message_scroll: Default::default(),
|
||||||
message_input: Default::default(),
|
message_input: Default::default(),
|
||||||
buttons: Default::default(),
|
buttons: Default::default(),
|
||||||
messages: Default::default(),
|
|
||||||
draft: String::new(),
|
draft: String::new(),
|
||||||
send_button: Default::default(),
|
send_button: Default::default(),
|
||||||
sorting: RoomSorting::Recent,
|
sorting: RoomSorting::Alphabetic,
|
||||||
sas_accept_button: Default::default(),
|
sas_accept_button: Default::default(),
|
||||||
sas_deny_button: Default::default(),
|
sas_deny_button: Default::default(),
|
||||||
}
|
}
|
||||||
@ -275,14 +386,11 @@ impl MainView {
|
|||||||
m.origin_server_ts()
|
m.origin_server_ts()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.max()
|
.min()
|
||||||
.copied();
|
.copied();
|
||||||
match time {
|
match time {
|
||||||
Some(time) => time,
|
Some(time) => time,
|
||||||
None => {
|
None => std::time::SystemTime::now(),
|
||||||
println!("couldn't get time");
|
|
||||||
std::time::SystemTime::now()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
RoomSorting::Alphabetic => list.sort_by_cached_key(|id| {
|
RoomSorting::Alphabetic => list.sort_by_cached_key(|id| {
|
||||||
@ -369,6 +477,9 @@ impl MainView {
|
|||||||
if let Some(ref sas) = self.sas {
|
if let Some(ref sas) = self.sas {
|
||||||
let device = sas.other_device();
|
let device = sas.other_device();
|
||||||
let sas_row = match sas.emoji() {
|
let sas_row = match sas.emoji() {
|
||||||
|
_ if sas.is_done() => {
|
||||||
|
Row::new().push(Text::new("Verification complete").width(Length::Fill))
|
||||||
|
}
|
||||||
Some(emojis) => {
|
Some(emojis) => {
|
||||||
let mut row = Row::new().push(Text::new("Verify emojis match:"));
|
let mut row = Row::new().push(Text::new("Verify emojis match:"));
|
||||||
for (emoji, name) in emojis.iter() {
|
for (emoji, name) in emojis.iter() {
|
||||||
@ -477,7 +588,7 @@ pub enum Message {
|
|||||||
LoginFailed(String),
|
LoginFailed(String),
|
||||||
|
|
||||||
// Main state messages
|
// Main state messages
|
||||||
ResetRooms(BTreeMap<RoomId, String>),
|
ResetRooms(BTreeMap<RoomId, RoomEntry>),
|
||||||
SelectRoom(RoomId),
|
SelectRoom(RoomId),
|
||||||
/// Set error message
|
/// Set error message
|
||||||
ErrorMessage(String),
|
ErrorMessage(String),
|
||||||
@ -732,19 +843,49 @@ impl Application for Retrix {
|
|||||||
*self = Retrix::Prompt(view);
|
*self = Retrix::Prompt(view);
|
||||||
}
|
}
|
||||||
Message::LoggedIn(client, session) => {
|
Message::LoggedIn(client, session) => {
|
||||||
*self = Retrix::LoggedIn(MainView::new(client, session));
|
*self = Retrix::LoggedIn(MainView::new(client.clone(), session));
|
||||||
/*let client = client.clone();
|
|
||||||
return Command::perform(
|
return Command::perform(
|
||||||
async move {
|
async move {
|
||||||
let mut rooms = BTreeMap::new();
|
let mut rooms: BTreeMap<RoomId, RoomEntry> = BTreeMap::new();
|
||||||
for (id, room) in client.joined_rooms().read().await.iter() {
|
for (id, room) in client.joined_rooms().read().await.iter() {
|
||||||
let name = room.read().await.display_name();
|
let room = room.read().await;
|
||||||
rooms.insert(id.to_owned(), name);
|
let entry = rooms.entry(id.clone()).or_default();
|
||||||
|
|
||||||
|
entry.direct = room.direct_target.clone();
|
||||||
|
// Display name calculation for DMs is bronk so we're doing it
|
||||||
|
// ourselves
|
||||||
|
match entry.direct {
|
||||||
|
Some(ref direct) => {
|
||||||
|
let request = matrix_sdk::api::r0::profile::get_display_name::Request::new(direct);
|
||||||
|
if let Ok(response) = client.send(request).await {
|
||||||
|
if let Some(name) = response.displayname {
|
||||||
|
entry.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => entry.name = room.display_name(),
|
||||||
|
}
|
||||||
|
let messages = room
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|event| match event {
|
||||||
|
AnyPossiblyRedactedSyncMessageEvent::Redacted(e) => {
|
||||||
|
AnyRoomEvent::RedactedMessage(
|
||||||
|
e.into_full_event(id.clone()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AnyPossiblyRedactedSyncMessageEvent::Regular(e) => {
|
||||||
|
AnyRoomEvent::Message(e.into_full_event(id.clone()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
entry.messages.messages = messages;
|
||||||
}
|
}
|
||||||
rooms
|
rooms
|
||||||
},
|
},
|
||||||
|rooms| Message::ResetRooms(rooms),
|
Message::ResetRooms,
|
||||||
);*/
|
);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
@ -756,7 +897,30 @@ impl Application for Retrix {
|
|||||||
Message::ResetRooms(r) => view.rooms = r,
|
Message::ResetRooms(r) => view.rooms = r,
|
||||||
Message::SelectRoom(r) => view.selected = Some(r),
|
Message::SelectRoom(r) => view.selected = Some(r),
|
||||||
Message::Sync(event) => match event {
|
Message::Sync(event) => match event {
|
||||||
matrix::Event::Room(_) => (),
|
matrix::Event::Room(event) => match event {
|
||||||
|
AnyRoomEvent::Message(event) => {
|
||||||
|
let room = view.rooms.entry(event.room_id().clone()).or_default();
|
||||||
|
room.messages
|
||||||
|
.push_back(AnyRoomEvent::Message(event.clone()));
|
||||||
|
}
|
||||||
|
AnyRoomEvent::State(event) => match event {
|
||||||
|
AnyStateEvent::RoomCanonicalAlias(alias) => {
|
||||||
|
let room = view.rooms.entry(alias.room_id).or_default();
|
||||||
|
room.alias = alias.content.alias;
|
||||||
|
}
|
||||||
|
AnyStateEvent::RoomName(name) => {
|
||||||
|
let room = view.rooms.entry(name.room_id).or_default();
|
||||||
|
room.display_name = name.content.name().map(String::from);
|
||||||
|
}
|
||||||
|
AnyStateEvent::RoomTopic(topic) => {
|
||||||
|
let room = view.rooms.entry(topic.room_id).or_default();
|
||||||
|
}
|
||||||
|
any => {
|
||||||
|
let room = view.rooms.entry(any.room_id().clone()).or_default();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
matrix::Event::ToDevice(event) => match event {
|
matrix::Event::ToDevice(event) => match event {
|
||||||
AnyToDeviceEvent::KeyVerificationStart(start) => {
|
AnyToDeviceEvent::KeyVerificationStart(start) => {
|
||||||
let client = view.client.clone();
|
let client = view.client.clone();
|
||||||
|
Loading…
Reference in New Issue
Block a user