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:
Amanda Graven 2020-12-10 13:17:41 +01:00
parent cf7c333d45
commit 686bf2892a
No known key found for this signature in database
GPG Key ID: 45C461CDC9286390
3 changed files with 225 additions and 24 deletions

View File

@ -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"

View File

@ -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
View File

@ -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();