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
					
				
					 3 changed files with 225 additions and 24 deletions
				
			
		|  | @ -23,6 +23,9 @@ tracing-subscriber = { version = "0.2", features = ["parking_lot"] } | |||
| 
 | ||||
| [dependencies.matrix-sdk] | ||||
| git = "https://github.com/matrix-org/matrix-rust-sdk" | ||||
| rev = "bca7f41" | ||||
| rev = "d9e5a17" | ||||
| 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 | ||||
| 
 | ||||
| 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 iced::{ | ||||
|  | @ -10,13 +13,30 @@ use matrix_sdk::{ | |||
|     events::{ | ||||
|         key::verification::cancel::CancelCode as VerificationCancelCode, | ||||
|         room::message::MessageEventContent, AnyMessageEventContent, | ||||
|         AnyPossiblyRedactedSyncMessageEvent, AnySyncMessageEvent, AnyToDeviceEvent, | ||||
|         AnyPossiblyRedactedSyncMessageEvent, AnyRoomEvent, AnyStateEvent, AnySyncMessageEvent, | ||||
|         AnyToDeviceEvent, | ||||
|     }, | ||||
|     identifiers::RoomId, | ||||
|     identifiers::{RoomAliasId, RoomId, UserId}, | ||||
| }; | ||||
| 
 | ||||
| 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
 | ||||
| #[derive(Debug, Clone, Default)] | ||||
| pub struct PromptView { | ||||
|  | @ -147,6 +167,99 @@ pub enum RoomSorting { | |||
|     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
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct MainView { | ||||
|  | @ -165,8 +278,7 @@ pub struct MainView { | |||
|     sas: Option<matrix_sdk::Sas>, | ||||
|     /// Whether to sort rooms alphabetically or by activity
 | ||||
|     sorting: RoomSorting, | ||||
|     rooms: BTreeMap<RoomId, String>, | ||||
|     messages: BTreeMap<RoomId, MessageEventContent>, | ||||
|     rooms: BTreeMap<RoomId, RoomEntry>, | ||||
| 
 | ||||
|     /// Room list entry/button to select room
 | ||||
|     buttons: HashMap<RoomId, iced::button::State>, | ||||
|  | @ -201,10 +313,9 @@ impl MainView { | |||
|             message_scroll: Default::default(), | ||||
|             message_input: Default::default(), | ||||
|             buttons: Default::default(), | ||||
|             messages: Default::default(), | ||||
|             draft: String::new(), | ||||
|             send_button: Default::default(), | ||||
|             sorting: RoomSorting::Recent, | ||||
|             sorting: RoomSorting::Alphabetic, | ||||
|             sas_accept_button: Default::default(), | ||||
|             sas_deny_button: Default::default(), | ||||
|         } | ||||
|  | @ -275,14 +386,11 @@ impl MainView { | |||
|                                 m.origin_server_ts() | ||||
|                             } | ||||
|                         }) | ||||
|                         .max() | ||||
|                         .min() | ||||
|                         .copied(); | ||||
|                     match time { | ||||
|                         Some(time) => time, | ||||
|                         None => { | ||||
|                             println!("couldn't get time"); | ||||
|                             std::time::SystemTime::now() | ||||
|                         } | ||||
|                         None => std::time::SystemTime::now(), | ||||
|                     } | ||||
|                 }), | ||||
|                 RoomSorting::Alphabetic => list.sort_by_cached_key(|id| { | ||||
|  | @ -369,6 +477,9 @@ impl MainView { | |||
|         if let Some(ref sas) = self.sas { | ||||
|             let device = sas.other_device(); | ||||
|             let sas_row = match sas.emoji() { | ||||
|                 _ if sas.is_done() => { | ||||
|                     Row::new().push(Text::new("Verification complete").width(Length::Fill)) | ||||
|                 } | ||||
|                 Some(emojis) => { | ||||
|                     let mut row = Row::new().push(Text::new("Verify emojis match:")); | ||||
|                     for (emoji, name) in emojis.iter() { | ||||
|  | @ -477,7 +588,7 @@ pub enum Message { | |||
|     LoginFailed(String), | ||||
| 
 | ||||
|     // Main state messages
 | ||||
|     ResetRooms(BTreeMap<RoomId, String>), | ||||
|     ResetRooms(BTreeMap<RoomId, RoomEntry>), | ||||
|     SelectRoom(RoomId), | ||||
|     /// Set error message
 | ||||
|     ErrorMessage(String), | ||||
|  | @ -732,19 +843,49 @@ impl Application for Retrix { | |||
|                     *self = Retrix::Prompt(view); | ||||
|                 } | ||||
|                 Message::LoggedIn(client, session) => { | ||||
|                     *self = Retrix::LoggedIn(MainView::new(client, session)); | ||||
|                     /*let client = client.clone();
 | ||||
|                     *self = Retrix::LoggedIn(MainView::new(client.clone(), session)); | ||||
|                     return Command::perform( | ||||
|                         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() { | ||||
|                                 let name = room.read().await.display_name(); | ||||
|                                 rooms.insert(id.to_owned(), name); | ||||
|                                 let room = room.read().await; | ||||
|                                 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| Message::ResetRooms(rooms), | ||||
|                     );*/ | ||||
|                         Message::ResetRooms, | ||||
|                     ); | ||||
|                 } | ||||
|                 _ => (), | ||||
|             }, | ||||
|  | @ -756,7 +897,30 @@ impl Application for Retrix { | |||
|                     Message::ResetRooms(r) => view.rooms = r, | ||||
|                     Message::SelectRoom(r) => view.selected = Some(r), | ||||
|                     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 { | ||||
|                             AnyToDeviceEvent::KeyVerificationStart(start) => { | ||||
|                                 let client = view.client.clone(); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue