Intial image support, proper time formatting
Also feature gate unix exclusive code
This commit is contained in:
		
							parent
							
								
									2a9dbb90d3
								
							
						
					
					
						commit
						f25d3b6821
					
				
					 4 changed files with 315 additions and 132 deletions
				
			
		
							
								
								
									
										14
									
								
								Cargo.toml
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								Cargo.toml
									
										
									
									
									
								
							|  | @ -8,22 +8,22 @@ edition = "2018" | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| anyhow = "1.0" | anyhow = "1.0" | ||||||
| async-trait = "0.1" | async-stream = "0.3" | ||||||
| crossbeam-channel = "0.4" |  | ||||||
| dirs-next = "2.0" | dirs-next = "2.0" | ||||||
| futures = "0.3" | futures = "0.3" | ||||||
| iced = { version = "0.2", features = ["debug", "tokio_old", "image"] } | iced = { git = "https://github.com/hecrj/iced", rev = "31522e3", features = ["debug", "image", "tokio"] } | ||||||
| iced_futures = "0.2" | iced_futures = { git = "https://github.com/hecrj/iced", rev = "31522e3" } | ||||||
| hostname = "0.3" | #iced_glow = { git = "https://github.com/hecrj/iced", rev = "31522e3", features = ["image"] } | ||||||
| #matrix-sdk-common-macros = { git = "https://github.com/matrix-org/matrix-rust-sdk", rev = "e65915e" } | #matrix-sdk-common-macros = { git = "https://github.com/matrix-org/matrix-rust-sdk", rev = "e65915e" } | ||||||
| serde = { version = "1.0", features = ["derive"] } | serde = { version = "1.0", features = ["derive"] } | ||||||
| tokio = { version = "0.2", features = ["sync"] } | time = "0.2" | ||||||
|  | tokio = { version = "1.0", features = ["sync"] } | ||||||
| toml = "0.5" | toml = "0.5" | ||||||
| tracing-subscriber = { version = "0.2", features = ["parking_lot"] } | 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 = "74998c8" | rev = "6435269" | ||||||
| default_features = false | default_features = false | ||||||
| features = ["encryption", "sqlite_cryptostore", "messages", "rustls-tls", "unstable-synapse-quirks"] | features = ["encryption", "sqlite_cryptostore", "messages", "rustls-tls", "unstable-synapse-quirks"] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,7 @@ | ||||||
| extern crate crossbeam_channel as channel; |  | ||||||
| extern crate dirs_next as dirs; | extern crate dirs_next as dirs; | ||||||
| 
 | 
 | ||||||
| use std::fs::Permissions; | #[cfg(unix)] | ||||||
| use std::os::unix::fs::PermissionsExt; | use std::{fs::Permissions, os::unix::fs::PermissionsExt}; | ||||||
| 
 | 
 | ||||||
| use iced::Application; | use iced::Application; | ||||||
| 
 | 
 | ||||||
|  | @ -16,6 +15,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     // Make sure config dir exists and is not accessible by other users.
 |     // Make sure config dir exists and is not accessible by other users.
 | ||||||
|     if !config_dir.is_dir() { |     if !config_dir.is_dir() { | ||||||
|         std::fs::create_dir(&config_dir)?; |         std::fs::create_dir(&config_dir)?; | ||||||
|  |         #[cfg(unix)] | ||||||
|         std::fs::set_permissions(&config_dir, Permissions::from_mode(0o700))?; |         std::fs::set_permissions(&config_dir, Permissions::from_mode(0o700))?; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,16 @@ | ||||||
| use std::time::{Duration, SystemTime}; | use std::{ | ||||||
|  |     convert::TryFrom, | ||||||
|  |     time::{Duration, SystemTime}, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|  | use async_stream::stream; | ||||||
| use matrix_sdk::{ | use matrix_sdk::{ | ||||||
|     api::r0::{account::register::Request as RegistrationRequest, uiaa::AuthData}, |     api::r0::{account::register::Request as RegistrationRequest, uiaa::AuthData}, | ||||||
|     events::{AnyRoomEvent, AnySyncRoomEvent, AnyToDeviceEvent}, |     events::{ | ||||||
|     identifiers::{DeviceId, EventId, UserId}, |         room::message::{MessageEvent, MessageEventContent}, | ||||||
|  |         AnyMessageEvent, AnyRoomEvent, AnySyncRoomEvent, AnyToDeviceEvent, | ||||||
|  |     }, | ||||||
|  |     identifiers::{DeviceId, EventId, ServerName, UserId}, | ||||||
|     reqwest::Url, |     reqwest::Url, | ||||||
|     Client, ClientConfig, LoopCtrl, SyncSettings, |     Client, ClientConfig, LoopCtrl, SyncSettings, | ||||||
| }; | }; | ||||||
|  | @ -67,6 +74,7 @@ pub async fn signup( | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     let response = client.register(request).await?; |     let response = client.register(request).await?; | ||||||
|  |     client.sync_once(SyncSettings::new()).await?; | ||||||
| 
 | 
 | ||||||
|     let session = Session { |     let session = Session { | ||||||
|         access_token: response.access_token.unwrap(), |         access_token: response.access_token.unwrap(), | ||||||
|  | @ -103,7 +111,7 @@ pub async fn login( | ||||||
|         homeserver: server.to_owned(), |         homeserver: server.to_owned(), | ||||||
|     }; |     }; | ||||||
|     write_session(&session)?; |     write_session(&session)?; | ||||||
|     //client.sync_once(SyncSettings::new()).await?;
 |     client.sync_once(SyncSettings::new()).await?; | ||||||
| 
 | 
 | ||||||
|     Ok((client, session)) |     Ok((client, session)) | ||||||
| } | } | ||||||
|  | @ -150,6 +158,19 @@ fn write_session(session: &Session) -> Result<(), Error> { | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Break down an mxc url to its authority and path
 | ||||||
|  | pub fn parse_mxc(url: &str) -> Result<(Box<ServerName>, String), Error> { | ||||||
|  |     let url = Url::parse(&url)?; | ||||||
|  |     anyhow::ensure!(url.scheme() == "mxc", "Not an mxc url"); | ||||||
|  |     let host = url.host_str().ok_or(anyhow::anyhow!("url"))?; | ||||||
|  |     let server_name: Box<ServerName> = <&ServerName>::try_from(host)?.into(); | ||||||
|  |     let path = url.path_segments().and_then(|mut p| p.next()); | ||||||
|  |     match path { | ||||||
|  |         Some(path) => Ok((server_name, path.to_owned())), | ||||||
|  |         _ => Err(anyhow::anyhow!("Invalid mxc url")), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| pub struct MatrixSync { | pub struct MatrixSync { | ||||||
|     client: matrix_sdk::Client, |     client: matrix_sdk::Client, | ||||||
|     join: Option<tokio::task::JoinHandle<()>>, |     join: Option<tokio::task::JoinHandle<()>>, | ||||||
|  | @ -162,21 +183,6 @@ impl MatrixSync { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*#[async_trait]
 |  | ||||||
| impl EventEmitter for Callback { |  | ||||||
|     async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent<MessageEventContent>) { |  | ||||||
|         let room_id = if let matrix_sdk::RoomState::Joined(arc) = room { |  | ||||||
|             let room = arc.read().await; |  | ||||||
|             room.room_id.clone() |  | ||||||
|         } else { |  | ||||||
|             return; |  | ||||||
|         }; |  | ||||||
|         self.sender |  | ||||||
|             .send(event.clone().into_full_event(room_id)) |  | ||||||
|             .ok(); |  | ||||||
|     } |  | ||||||
| }*/ |  | ||||||
| 
 |  | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| pub enum Event { | pub enum Event { | ||||||
|     Room(AnyRoomEvent), |     Room(AnyRoomEvent), | ||||||
|  | @ -201,13 +207,13 @@ where | ||||||
|         mut self: Box<Self>, |         mut self: Box<Self>, | ||||||
|         _input: iced_futures::BoxStream<I>, |         _input: iced_futures::BoxStream<I>, | ||||||
|     ) -> iced_futures::BoxStream<Self::Output> { |     ) -> iced_futures::BoxStream<Self::Output> { | ||||||
|         let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); |         let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); | ||||||
|         let client = self.client.clone(); |         let client = self.client.clone(); | ||||||
|         let join = tokio::task::spawn(async move { |         let join = tokio::task::spawn(async move { | ||||||
|             client |             client | ||||||
|                 .sync_with_callback( |                 .sync_with_callback( | ||||||
|                     SyncSettings::new() |                     SyncSettings::new() | ||||||
|                         //.token(client.sync_token().await.unwrap())
 |                         .token(client.sync_token().await.unwrap()) | ||||||
|                         .timeout(Duration::from_secs(90)) |                         .timeout(Duration::from_secs(90)) | ||||||
|                         .full_state(true), |                         .full_state(true), | ||||||
|                     |response| async { |                     |response| async { | ||||||
|  | @ -241,14 +247,22 @@ where | ||||||
|                 .await; |                 .await; | ||||||
|         }); |         }); | ||||||
|         self.join = Some(join); |         self.join = Some(join); | ||||||
|         Box::pin(receiver) |         let stream = stream! { | ||||||
|  |             while let Some(item) = receiver.recv().await { | ||||||
|  |                 yield item; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         Box::pin(stream) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub trait AnyRoomEventExt { | pub trait AnyRoomEventExt { | ||||||
|  |     /// Gets the event id of the underlying event
 | ||||||
|     fn event_id(&self) -> &EventId; |     fn event_id(&self) -> &EventId; | ||||||
|     /// Gets the ´origin_server_ts` member of the underlying event
 |     /// Gets the ´origin_server_ts` member of the underlying event
 | ||||||
|     fn origin_server_ts(&self) -> SystemTime; |     fn origin_server_ts(&self) -> SystemTime; | ||||||
|  |     /// Gets the mxc url in a message event if there is noe
 | ||||||
|  |     fn image_url(&self) -> Option<String>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl AnyRoomEventExt for AnyRoomEvent { | impl AnyRoomEventExt for AnyRoomEvent { | ||||||
|  | @ -269,4 +283,26 @@ impl AnyRoomEventExt for AnyRoomEvent { | ||||||
|         } |         } | ||||||
|         .to_owned() |         .to_owned() | ||||||
|     } |     } | ||||||
|  |     fn image_url(&self) -> Option<String> { | ||||||
|  |         match self { | ||||||
|  |             AnyRoomEvent::Message(message) => message.image_url(), | ||||||
|  |             _ => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub trait AnyMessageEventExt { | ||||||
|  |     fn image_url(&self) -> Option<String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl AnyMessageEventExt for AnyMessageEvent { | ||||||
|  |     fn image_url(&self) -> Option<String> { | ||||||
|  |         match self { | ||||||
|  |             AnyMessageEvent::RoomMessage(MessageEvent { | ||||||
|  |                 content: MessageEventContent::Image(ref image), | ||||||
|  |                 .. | ||||||
|  |             }) => image.url.clone(), | ||||||
|  |             _ => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										319
									
								
								src/ui.rs
									
										
									
									
									
								
							
							
						
						
									
										319
									
								
								src/ui.rs
									
										
									
									
									
								
							|  | @ -5,22 +5,23 @@ use std::{ | ||||||
| 
 | 
 | ||||||
| use futures::executor::block_on; | use futures::executor::block_on; | ||||||
| use iced::{ | use iced::{ | ||||||
|     Application, Button, Column, Command, Container, Element, Length, Row, Rule, Scrollable, |     Align, Application, Button, Column, Command, Container, Element, Length, Row, Rule, Scrollable, | ||||||
|     Subscription, Text, TextInput, |     Subscription, Text, TextInput, | ||||||
| }; | }; | ||||||
| use matrix_sdk::{ | use matrix_sdk::{ | ||||||
|     api::r0::message::get_message_events::{ |     api::r0::{ | ||||||
|         Request as MessageRequest, Response as MessageResponse, |         media::get_content::Request as ImageRequest, | ||||||
|  |         message::get_message_events::{Request as MessageRequest, Response as MessageResponse}, | ||||||
|     }, |     }, | ||||||
|     events::{ |     events::{ | ||||||
|         key::verification::cancel::CancelCode as VerificationCancelCode, |         key::verification::cancel::CancelCode as VerificationCancelCode, | ||||||
|         room::message::MessageEventContent, AnyMessageEvent, AnyMessageEventContent, AnyRoomEvent, |         room::{member::MembershipState, message::MessageEventContent}, | ||||||
|         AnyStateEvent, AnyToDeviceEvent, |         AnyMessageEvent, AnyMessageEventContent, AnyRoomEvent, AnyStateEvent, AnyToDeviceEvent, | ||||||
|     }, |     }, | ||||||
|     identifiers::{EventId, RoomAliasId, RoomId, UserId}, |     identifiers::{EventId, RoomAliasId, RoomId, UserId}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::matrix::{self, AnyRoomEventExt}; | use crate::matrix::{self, AnyMessageEventExt, AnyRoomEventExt}; | ||||||
| 
 | 
 | ||||||
| pub mod prompt; | pub mod prompt; | ||||||
| pub mod settings; | pub mod settings; | ||||||
|  | @ -39,31 +40,28 @@ pub enum RoomSorting { | ||||||
| #[derive(Clone, Debug, Default)] | #[derive(Clone, Debug, Default)] | ||||||
| pub struct RoomEntry { | pub struct RoomEntry { | ||||||
|     /// Cached calculated name
 |     /// Cached calculated name
 | ||||||
|     name: String, |     pub name: String, | ||||||
|     /// Room topic
 |     /// Room topic
 | ||||||
|     topic: String, |     pub topic: String, | ||||||
|     /// Canonical alias
 |     /// Canonical alias
 | ||||||
|     alias: Option<RoomAliasId>, |     pub alias: Option<RoomAliasId>, | ||||||
|     /// Defined display name
 |     /// Defined display name
 | ||||||
|     display_name: Option<String>, |     pub display_name: Option<String>, | ||||||
|     /// Person we're in a direct message with
 |     /// Person we're in a direct message with
 | ||||||
|     direct: Option<UserId>, |     pub direct: Option<UserId>, | ||||||
|     /// Cache of messages
 |     /// Cache of messages
 | ||||||
|     messages: MessageBuffer, |     pub messages: MessageBuffer, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl RoomEntry { | impl RoomEntry { | ||||||
|     /// Recalculate displayname
 |     pub fn from_sdk(room: &matrix_sdk::JoinedRoom) -> Self { | ||||||
|     pub fn update_display_name(&mut self, id: &RoomId) { |         Self { | ||||||
|         self.name = if let Some(ref name) = self.display_name { |             direct: room.direct_target(), | ||||||
|             name.to_owned() |             name: block_on(async { room.display_name().await }), | ||||||
|         } else if let Some(ref user) = self.direct { |             topic: room.topic().unwrap_or_default(), | ||||||
|             user.to_string() |             alias: room.canonical_alias(), | ||||||
|         } else if let Some(ref alias) = self.alias { |             ..Default::default() | ||||||
|             alias.to_string() |         } | ||||||
|         } else { |  | ||||||
|             id.to_string() |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -82,6 +80,8 @@ pub struct MessageBuffer { | ||||||
|     end: Option<String>, |     end: Option<String>, | ||||||
|     /// Most recent activity in the room
 |     /// Most recent activity in the room
 | ||||||
|     updated: std::time::SystemTime, |     updated: std::time::SystemTime, | ||||||
|  |     /// Whether we're awaiting for backfill to be received
 | ||||||
|  |     loading: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl MessageBuffer { | impl MessageBuffer { | ||||||
|  | @ -117,6 +117,13 @@ impl MessageBuffer { | ||||||
|         self.sort(); |         self.sort(); | ||||||
|         self.update_time(); |         self.update_time(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /// Whather the message buffer has the room creation event
 | ||||||
|  |     pub fn has_beginning(&self) -> bool { | ||||||
|  |         self.messages | ||||||
|  |             .iter() | ||||||
|  |             .any(|e| matches!(e, AnyRoomEvent::State(AnyStateEvent::RoomCreate(_)))) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for MessageBuffer { | impl Default for MessageBuffer { | ||||||
|  | @ -127,6 +134,7 @@ impl Default for MessageBuffer { | ||||||
|             start: None, |             start: None, | ||||||
|             end: None, |             end: None, | ||||||
|             updated: SystemTime::UNIX_EPOCH, |             updated: SystemTime::UNIX_EPOCH, | ||||||
|  |             loading: false, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -153,6 +161,8 @@ pub struct MainView { | ||||||
|     sorting: RoomSorting, |     sorting: RoomSorting, | ||||||
|     /// Room state
 |     /// Room state
 | ||||||
|     rooms: BTreeMap<RoomId, RoomEntry>, |     rooms: BTreeMap<RoomId, RoomEntry>, | ||||||
|  |     /// A map of mxc urls to image data
 | ||||||
|  |     images: BTreeMap<String, iced::image::Handle>, | ||||||
| 
 | 
 | ||||||
|     /// Room list entries for direct conversations
 |     /// Room list entries for direct conversations
 | ||||||
|     dm_buttons: Vec<iced::button::State>, |     dm_buttons: Vec<iced::button::State>, | ||||||
|  | @ -162,6 +172,10 @@ pub struct MainView { | ||||||
|     room_scroll: iced::scrollable::State, |     room_scroll: iced::scrollable::State, | ||||||
|     /// Message view scrollbar state
 |     /// Message view scrollbar state
 | ||||||
|     message_scroll: iced::scrollable::State, |     message_scroll: iced::scrollable::State, | ||||||
|  |     /// Backfill fetch button state
 | ||||||
|  |     backfill_button: iced::button::State, | ||||||
|  |     /// Button to go the room a tombstone points to
 | ||||||
|  |     tombstone_button: iced::button::State, | ||||||
|     /// Message draft text input
 |     /// Message draft text input
 | ||||||
|     message_input: iced::text_input::State, |     message_input: iced::text_input::State, | ||||||
|     /// Button to send drafted message
 |     /// Button to send drafted message
 | ||||||
|  | @ -186,8 +200,11 @@ impl MainView { | ||||||
|             sas: None, |             sas: None, | ||||||
|             rooms: Default::default(), |             rooms: Default::default(), | ||||||
|             selected: None, |             selected: None, | ||||||
|  |             images: Default::default(), | ||||||
|             room_scroll: Default::default(), |             room_scroll: Default::default(), | ||||||
|             message_scroll: Default::default(), |             message_scroll: Default::default(), | ||||||
|  |             backfill_button: Default::default(), | ||||||
|  |             tombstone_button: Default::default(), | ||||||
|             message_input: Default::default(), |             message_input: Default::default(), | ||||||
|             dm_buttons: Vec::new(), |             dm_buttons: Vec::new(), | ||||||
|             group_buttons: Vec::new(), |             group_buttons: Vec::new(), | ||||||
|  | @ -245,7 +262,12 @@ impl MainView { | ||||||
|             .map(|(idx, button)| { |             .map(|(idx, button)| { | ||||||
|                 // TODO: highlight selected
 |                 // TODO: highlight selected
 | ||||||
|                 let (id, room) = dm_rooms[idx]; |                 let (id, room) = dm_rooms[idx]; | ||||||
|                 Button::new(button, Text::new(&room.name)) |                 let name = if room.name.is_empty() { | ||||||
|  |                     "Missing name" | ||||||
|  |                 } else { | ||||||
|  |                     &room.name | ||||||
|  |                 }; | ||||||
|  |                 Button::new(button, Text::new(name)) | ||||||
|                     .width(300.into()) |                     .width(300.into()) | ||||||
|                     .on_press(Message::SelectRoom(id.to_owned())) |                     .on_press(Message::SelectRoom(id.to_owned())) | ||||||
|             }) |             }) | ||||||
|  | @ -281,10 +303,17 @@ impl MainView { | ||||||
| 
 | 
 | ||||||
|         let mut message_col = Column::new().spacing(5).padding(5); |         let mut message_col = Column::new().spacing(5).padding(5); | ||||||
|         let selected_room = match self.selected { |         let selected_room = match self.selected { | ||||||
|             Some(ref selected) => self.rooms.get(selected), |             Some(ref selected) => match ( | ||||||
|  |                 self.rooms.get(selected), | ||||||
|  |                 self.client.get_joined_room(selected), | ||||||
|  |             ) { | ||||||
|  |                 (Some(room), Some(joined)) => Some((room, joined)), | ||||||
|  |                 _ => None, | ||||||
|  |             }, | ||||||
|             None => None, |             None => None, | ||||||
|         }; |         }; | ||||||
|         if let Some(room) = selected_room { |         if let Some((room, joined)) = selected_room { | ||||||
|  |             // Include user id or canonical alias in title when appropriate
 | ||||||
|             let title = if let Some(ref direct) = room.direct { |             let title = if let Some(ref direct) = room.direct { | ||||||
|                 format!("{} ({})", &room.name, direct) |                 format!("{} ({})", &room.name, direct) | ||||||
|             } else if let Some(ref alias) = room.alias { |             } else if let Some(ref alias) = room.alias { | ||||||
|  | @ -292,28 +321,42 @@ impl MainView { | ||||||
|             } else { |             } else { | ||||||
|                 room.name.clone() |                 room.name.clone() | ||||||
|             }; |             }; | ||||||
|  | 
 | ||||||
|             message_col = message_col |             message_col = message_col | ||||||
|                 .push(Text::new(title).size(25)) |                 .push(Text::new(title).size(25)) | ||||||
|                 .push(Rule::horizontal(2)); |                 .push(Rule::horizontal(2)); | ||||||
|             let mut scroll = Scrollable::new(&mut self.message_scroll) |             let mut scroll = Scrollable::new(&mut self.message_scroll) | ||||||
|                 .scrollbar_width(2) |                 .scrollbar_width(2) | ||||||
|                 .height(Length::Fill); |                 .height(Length::Fill); | ||||||
|  |             // Backfill button or loading message
 | ||||||
|  |             let backfill: Element<_> = if room.messages.loading { | ||||||
|  |                 Text::new("Loading...").into() | ||||||
|  |             } else if room.messages.has_beginning() { | ||||||
|  |                 let creation = joined.create_content().unwrap(); | ||||||
|  |                 let mut col = | ||||||
|  |                     Column::new().push(Text::new("This is the beginning of room history")); | ||||||
|  |                 if let Some(prevous) = creation.predecessor { | ||||||
|  |                     col = col.push( | ||||||
|  |                         Button::new(&mut self.backfill_button, Text::new("Go to older version")) | ||||||
|  |                             .on_press(Message::SelectRoom(prevous.room_id)), | ||||||
|  |                     ); | ||||||
|  |                 } | ||||||
|  |                 col.into() | ||||||
|  |             } else { | ||||||
|  |                 Button::new(&mut self.backfill_button, Text::new("Load more messages")) | ||||||
|  |                     .on_press(Message::BackFill(self.selected.clone().unwrap())) | ||||||
|  |                     .into() | ||||||
|  |             }; | ||||||
|  |             scroll = scroll.push(Container::new(backfill).width(Length::Fill).center_x()); | ||||||
|  |             // Messages
 | ||||||
|             for event in room.messages.messages.iter() { |             for event in room.messages.messages.iter() { | ||||||
|                 #[allow(clippy::single_match)] |                 #[allow(clippy::single_match)] | ||||||
|                 match event { |                 match event { | ||||||
|                     AnyRoomEvent::Message(AnyMessageEvent::RoomMessage(message)) => { |                     AnyRoomEvent::Message(AnyMessageEvent::RoomMessage(message)) => { | ||||||
|                         let sender = { |                         let sender = | ||||||
|                             match self.client.get_joined_room(&message.room_id) { |                             match block_on(async { joined.get_member(&message.sender).await }) { | ||||||
|                                 Some(backend) => { |  | ||||||
|                                     match block_on(async { |  | ||||||
|                                         backend.get_member(&message.sender).await |  | ||||||
|                                     }) { |  | ||||||
|                                 Some(member) => member.name().to_owned(), |                                 Some(member) => member.name().to_owned(), | ||||||
|                                 None => message.sender.to_string(), |                                 None => message.sender.to_string(), | ||||||
|                                     } |  | ||||||
|                                 } |  | ||||||
|                                 None => message.sender.to_string(), |  | ||||||
|                             } |  | ||||||
|                             }; |                             }; | ||||||
|                         let content: Element<_> = match &message.content { |                         let content: Element<_> = match &message.content { | ||||||
|                             MessageEventContent::Audio(audio) => { |                             MessageEventContent::Audio(audio) => { | ||||||
|  | @ -323,7 +366,7 @@ impl MainView { | ||||||
|                                     .into() |                                     .into() | ||||||
|                             } |                             } | ||||||
|                             MessageEventContent::Emote(emote) => { |                             MessageEventContent::Emote(emote) => { | ||||||
|                                 Text::new(format!("{} {}", sender, emote.body)) |                                 Text::new(format!("* {} {}", sender, emote.body)) | ||||||
|                                     .width(Length::Fill) |                                     .width(Length::Fill) | ||||||
|                                     .into() |                                     .into() | ||||||
|                             } |                             } | ||||||
|  | @ -334,10 +377,25 @@ impl MainView { | ||||||
|                                     .into() |                                     .into() | ||||||
|                             } |                             } | ||||||
|                             MessageEventContent::Image(image) => { |                             MessageEventContent::Image(image) => { | ||||||
|                                 Text::new(format!("Image with description: {}", image.body)) |                                 if let Some(ref url) = image.url { | ||||||
|  |                                     match self.images.get(url) { | ||||||
|  |                                         Some(handle) => Container::new( | ||||||
|  |                                             iced::Image::new(handle.to_owned()) | ||||||
|  |                                                 .width(800.into()) | ||||||
|  |                                                 .height(1200.into()), | ||||||
|  |                                         ) | ||||||
|  |                                         .width(Length::Fill) | ||||||
|  |                                         .into(), | ||||||
|  |                                         None => { | ||||||
|  |                                             Text::new("Image not loaded").width(Length::Fill).into() | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                 } else { | ||||||
|  |                                     Text::new("Encrypted images not supported yet") | ||||||
|                                         .width(Length::Fill) |                                         .width(Length::Fill) | ||||||
|                                         .into() |                                         .into() | ||||||
|                                 } |                                 } | ||||||
|  |                             } | ||||||
|                             MessageEventContent::Notice(notice) => { |                             MessageEventContent::Notice(notice) => { | ||||||
|                                 Text::new(¬ice.body).width(Length::Fill).into() |                                 Text::new(¬ice.body).width(Length::Fill).into() | ||||||
|                             } |                             } | ||||||
|  | @ -367,6 +425,26 @@ impl MainView { | ||||||
|                     _ => (), |                     _ => (), | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             // Tombstone
 | ||||||
|  |             if let Some(tombstone) = joined.tombstone() { | ||||||
|  |                 let text = Text::new(format!( | ||||||
|  |                     "This room has been upgraded to a new version: {}", | ||||||
|  |                     tombstone.body | ||||||
|  |                 )); | ||||||
|  |                 let button = | ||||||
|  |                     Button::new(&mut self.tombstone_button, Text::new("Go to upgraded room")) | ||||||
|  |                         .on_press(Message::SelectRoom(tombstone.replacement_room)); | ||||||
|  |                 scroll = scroll.push( | ||||||
|  |                     Container::new( | ||||||
|  |                         Column::new() | ||||||
|  |                             .push(text) | ||||||
|  |                             .push(button) | ||||||
|  |                             .align_items(Align::Center), | ||||||
|  |                     ) | ||||||
|  |                     .center_x() | ||||||
|  |                     .width(Length::Fill), | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|             message_col = message_col.push(scroll); |             message_col = message_col.push(scroll); | ||||||
|         } else { |         } else { | ||||||
|             message_col = message_col.push( |             message_col = message_col.push( | ||||||
|  | @ -490,6 +568,10 @@ pub enum Message { | ||||||
|     BackFill(RoomId), |     BackFill(RoomId), | ||||||
|     /// Received backfille
 |     /// Received backfille
 | ||||||
|     BackFilled(RoomId, MessageResponse), |     BackFilled(RoomId, MessageResponse), | ||||||
|  |     /// Fetch an image pointed to by an mxc url
 | ||||||
|  |     FetchImage(String), | ||||||
|  |     /// Fetched an image
 | ||||||
|  |     FetchedImage(String, iced::image::Handle), | ||||||
|     /// View messages from this room
 |     /// View messages from this room
 | ||||||
|     SelectRoom(RoomId), |     SelectRoom(RoomId), | ||||||
|     /// Set error message
 |     /// Set error message
 | ||||||
|  | @ -637,48 +719,25 @@ impl Application for Retrix { | ||||||
|                     *self = Retrix::LoggedIn(MainView::new(client.clone(), session)); |                     *self = Retrix::LoggedIn(MainView::new(client.clone(), session)); | ||||||
|                     let mut commands: Vec<Command<Message>> = Vec::new(); |                     let mut commands: Vec<Command<Message>> = Vec::new(); | ||||||
|                     for room in client.joined_rooms().into_iter() { |                     for room in client.joined_rooms().into_iter() { | ||||||
|                         let command = async move { |                         //let client = client.clone();
 | ||||||
|  |                         let command: Command<_> = async move { | ||||||
|                             let room = room.clone(); |                             let room = room.clone(); | ||||||
|                             let entry = RoomEntry { |                             let entry = RoomEntry::from_sdk(&room); | ||||||
|                                 direct: room.direct_target(), |  | ||||||
|                                 name: block_on(async { room.calculate_name().await }), |  | ||||||
|                                 topic: room.topic().unwrap_or_default(), |  | ||||||
|                                 ..RoomEntry::default() |  | ||||||
|                             }; |  | ||||||
| 
 | 
 | ||||||
|                             // Display name calculation for DMs is bronk so we're doing it
 |                             // Display name calculation for DMs is bronk so we're doing it
 | ||||||
|                             // ourselves
 |                             // ourselves
 | ||||||
|                             /*match entry.direct {
 |                             /*if let Some(ref direct) = entry.direct {
 | ||||||
|                                 Some(ref direct) => { |                                 let request = DisplayNameRequest::new(direct); | ||||||
|                                     let request = matrix_sdk::api::r0::profile::get_display_name::Request::new(direct); |  | ||||||
|                                 if let Ok(response) = client.send(request).await { |                                 if let Ok(response) = client.send(request).await { | ||||||
|                                     if let Some(name) = response.displayname { |                                     if let Some(name) = response.displayname { | ||||||
|                                         entry.name = name; |                                         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;*/ |  | ||||||
|                             Message::ResetRoom(room.room_id().to_owned(), entry) |                             Message::ResetRoom(room.room_id().to_owned(), entry) | ||||||
|                         } |                         } | ||||||
|                         .into(); |                         .into(); | ||||||
|                         commands.push(command) |                         commands.push(command); | ||||||
|                     } |                     } | ||||||
|                     return Command::batch(commands); |                     return Command::batch(commands); | ||||||
|                 } |                 } | ||||||
|  | @ -689,39 +748,58 @@ impl Application for Retrix { | ||||||
|                 Message::ClearError => view.error = None, |                 Message::ClearError => view.error = None, | ||||||
|                 Message::SetSort(s) => view.sorting = s, |                 Message::SetSort(s) => view.sorting = s, | ||||||
|                 Message::ResetRoom(id, room) => { |                 Message::ResetRoom(id, room) => { | ||||||
|                     view.rooms.insert(id, room).and(Some(())).unwrap_or(()) |                     view.rooms.insert(id.clone(), room); | ||||||
|  |                     return async move { Message::BackFill(id) }.into(); | ||||||
|                 } |                 } | ||||||
|                 Message::SelectRoom(r) => { |                 Message::SelectRoom(r) => { | ||||||
|                     view.selected = Some(r.clone()); |                     view.selected = Some(r.clone()); | ||||||
|  |                     if view.rooms.get(&r).unwrap().messages.messages.is_empty() { | ||||||
|                         return async move { Message::BackFill(r) }.into(); |                         return async move { Message::BackFill(r) }.into(); | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|                 Message::Sync(event) => match event { |                 Message::Sync(event) => match event { | ||||||
|                     matrix::Event::Room(event) => match event { |                     matrix::Event::Room(event) => match event { | ||||||
|                         AnyRoomEvent::Message(event) => { |                         AnyRoomEvent::Message(event) => { | ||||||
|                             let room = view.rooms.entry(event.room_id().clone()).or_default(); |                             let room = view.rooms.entry(event.room_id().clone()).or_default(); | ||||||
|                             room.messages.push(AnyRoomEvent::Message(event.clone())); |                             room.messages.push(AnyRoomEvent::Message(event.clone())); | ||||||
|  |                             let mut commands = Vec::new(); | ||||||
|  |                             let img_cmd = match event.image_url() { | ||||||
|  |                                 Some(url) => async { Message::FetchImage(url) }.into(), | ||||||
|  |                                 None => Command::none(), | ||||||
|  |                             }; | ||||||
|  |                             commands.push(img_cmd); | ||||||
|                             // Set read marker if message is in selected room
 |                             // Set read marker if message is in selected room
 | ||||||
|                             if view.selected.as_ref() == Some(event.room_id()) { |                             if view.selected.as_ref() == Some(event.room_id()) { | ||||||
|  |                                 // Super duper gross ugly scroll to bottom hack
 | ||||||
|  |                                 /*view.message_scroll = unsafe {
 | ||||||
|  |                                     let mut tmp = std::mem::transmute::<_, (Option<f32>, f32)>( | ||||||
|  |                                         view.message_scroll, | ||||||
|  |                                     ); | ||||||
|  |                                     tmp.1 = 999999.0; | ||||||
|  |                                     std::mem::transmute::<_, iced::scrollable::State>(tmp) | ||||||
|  |                                 };*/ | ||||||
|  | 
 | ||||||
|                                 let client = view.client.clone(); |                                 let client = view.client.clone(); | ||||||
|                                 return Command::perform( |                                 let marker_cmd = async move { | ||||||
|                                     async move { |                                     let result = client | ||||||
|                                         client |  | ||||||
|                                         .read_marker( |                                         .read_marker( | ||||||
|                                             event.room_id(), |                                             event.room_id(), | ||||||
|                                             event.event_id(), |                                             event.event_id(), | ||||||
|                                             Some(event.event_id()), |                                             Some(event.event_id()), | ||||||
|                                         ) |                                         ) | ||||||
|                                         .await |                                         .await | ||||||
|                                             .err() |                                         .err(); | ||||||
|                                     }, |                                     match result { | ||||||
|                                     |result| match result { |  | ||||||
|                                         Some(err) => Message::ErrorMessage(err.to_string()), |                                         Some(err) => Message::ErrorMessage(err.to_string()), | ||||||
|                                         // TODO: Make this an actual no-op
 |                                         // TODO: Make this an actual no-op
 | ||||||
|                                         None => Message::Login, |                                         None => Message::Login, | ||||||
|                                     }, |  | ||||||
|                                 ); |  | ||||||
|                                     } |                                     } | ||||||
|                                 } |                                 } | ||||||
|  |                                 .into(); | ||||||
|  |                                 commands.push(marker_cmd); | ||||||
|  |                             } | ||||||
|  |                             return Command::batch(commands); | ||||||
|  |                         } | ||||||
|                         AnyRoomEvent::State(event) => match event { |                         AnyRoomEvent::State(event) => match event { | ||||||
|                             AnyStateEvent::RoomCanonicalAlias(ref alias) => { |                             AnyStateEvent::RoomCanonicalAlias(ref alias) => { | ||||||
|                                 let room = view.rooms.entry(alias.room_id.clone()).or_default(); |                                 let room = view.rooms.entry(alias.room_id.clone()).or_default(); | ||||||
|  | @ -731,6 +809,9 @@ impl Application for Retrix { | ||||||
|                             AnyStateEvent::RoomName(ref name) => { |                             AnyStateEvent::RoomName(ref name) => { | ||||||
|                                 let room = view.rooms.entry(name.room_id.clone()).or_default(); |                                 let room = view.rooms.entry(name.room_id.clone()).or_default(); | ||||||
|                                 room.display_name = name.content.name().map(String::from); |                                 room.display_name = name.content.name().map(String::from); | ||||||
|  |                                 if let Some(joined) = view.client.get_joined_room(&name.room_id) { | ||||||
|  |                                     room.name = block_on(async { joined.display_name().await }); | ||||||
|  |                                 } | ||||||
|                                 room.messages.push(AnyRoomEvent::State(event)); |                                 room.messages.push(AnyRoomEvent::State(event)); | ||||||
|                             } |                             } | ||||||
|                             AnyStateEvent::RoomTopic(ref topic) => { |                             AnyStateEvent::RoomTopic(ref topic) => { | ||||||
|  | @ -738,6 +819,36 @@ impl Application for Retrix { | ||||||
|                                 room.topic = topic.content.topic.clone(); |                                 room.topic = topic.content.topic.clone(); | ||||||
|                                 room.messages.push(AnyRoomEvent::State(event)); |                                 room.messages.push(AnyRoomEvent::State(event)); | ||||||
|                             } |                             } | ||||||
|  |                             AnyStateEvent::RoomCreate(ref create) => { | ||||||
|  |                                 // Add room to the entry list
 | ||||||
|  |                                 let room = match view.client.get_joined_room(&create.room_id) { | ||||||
|  |                                     Some(joined) => view | ||||||
|  |                                         .rooms | ||||||
|  |                                         .entry(create.room_id.clone()) | ||||||
|  |                                         .or_insert_with(|| RoomEntry::from_sdk(&joined)), | ||||||
|  |                                     None => view.rooms.entry(create.room_id.clone()).or_default(), | ||||||
|  |                                 }; | ||||||
|  |                                 room.messages.push(AnyRoomEvent::State(event)); | ||||||
|  |                             } | ||||||
|  |                             AnyStateEvent::RoomMember(ref member) => { | ||||||
|  |                                 let room = view.rooms.entry(member.room_id.clone()).or_default(); | ||||||
|  |                                 let client = view.client.clone(); | ||||||
|  |                                 // If we left a room, remove it from the RoomEntry list
 | ||||||
|  |                                 let own_id = block_on(async { client.user_id().await }) | ||||||
|  |                                     .unwrap() | ||||||
|  |                                     .to_string(); | ||||||
|  |                                 if member.content.membership == MembershipState::Leave | ||||||
|  |                                     && member.state_key == own_id | ||||||
|  |                                 { | ||||||
|  |                                     // Deselect room if we're leaving selected room
 | ||||||
|  |                                     if view.selected.as_ref() == Some(&member.room_id) { | ||||||
|  |                                         view.selected = None; | ||||||
|  |                                     } | ||||||
|  |                                     view.rooms.remove(&member.room_id); | ||||||
|  |                                     return Command::none(); | ||||||
|  |                                 } | ||||||
|  |                                 room.messages.push(AnyRoomEvent::State(event)); | ||||||
|  |                             } | ||||||
|                             ref any => { |                             ref any => { | ||||||
|                                 // Ensure room exists
 |                                 // Ensure room exists
 | ||||||
|                                 let room = view.rooms.entry(any.room_id().clone()).or_default(); |                                 let room = view.rooms.entry(any.room_id().clone()).or_default(); | ||||||
|  | @ -768,7 +879,8 @@ impl Application for Retrix { | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 Message::BackFill(id) => { |                 Message::BackFill(id) => { | ||||||
|                     let room = view.rooms.get(&id).unwrap(); |                     let room = view.rooms.entry(id.clone()).or_default(); | ||||||
|  |                     room.messages.loading = true; | ||||||
|                     let client = view.client.clone(); |                     let client = view.client.clone(); | ||||||
|                     let token = match room.messages.end.clone() { |                     let token = match room.messages.end.clone() { | ||||||
|                         Some(end) => end, |                         Some(end) => end, | ||||||
|  | @ -789,6 +901,7 @@ impl Application for Retrix { | ||||||
|                 } |                 } | ||||||
|                 Message::BackFilled(id, response) => { |                 Message::BackFilled(id, response) => { | ||||||
|                     let room = view.rooms.get_mut(&id).unwrap(); |                     let room = view.rooms.get_mut(&id).unwrap(); | ||||||
|  |                     room.messages.loading = false; | ||||||
|                     let events: Vec<AnyRoomEvent> = response |                     let events: Vec<AnyRoomEvent> = response | ||||||
|                         .chunk |                         .chunk | ||||||
|                         .into_iter() |                         .into_iter() | ||||||
|  | @ -807,7 +920,41 @@ impl Application for Retrix { | ||||||
|                     if let Some(end) = response.end { |                     if let Some(end) = response.end { | ||||||
|                         room.messages.end = Some(end); |                         room.messages.end = Some(end); | ||||||
|                     } |                     } | ||||||
|  |                     let commands: Vec<Command<_>> = events | ||||||
|  |                         .iter() | ||||||
|  |                         .filter_map(|e| e.image_url()) | ||||||
|  |                         .map(|url| async { Message::FetchImage(url) }.into()) | ||||||
|  |                         .collect(); | ||||||
|                     room.messages.append(events); |                     room.messages.append(events); | ||||||
|  |                     return Command::batch(commands); | ||||||
|  |                 } | ||||||
|  |                 Message::FetchImage(url) => { | ||||||
|  |                     let (server, path) = match matrix::parse_mxc(&url) { | ||||||
|  |                         Ok((server, path)) => (server, path), | ||||||
|  |                         Err(e) => { | ||||||
|  |                             return async move { Message::ErrorMessage(e.to_string()) }.into() | ||||||
|  |                         } | ||||||
|  |                     }; | ||||||
|  |                     println!( | ||||||
|  |                         "Getting '{}' from '{}', with url '{}'", | ||||||
|  |                         &server, &path, &url | ||||||
|  |                     ); | ||||||
|  |                     let client = view.client.clone(); | ||||||
|  |                     return async move { | ||||||
|  |                         let request = ImageRequest::new(&path, &*server); | ||||||
|  |                         let response = client.send(request).await; | ||||||
|  |                         match response { | ||||||
|  |                             Ok(response) => Message::FetchedImage( | ||||||
|  |                                 url, | ||||||
|  |                                 iced::image::Handle::from_memory(response.file), | ||||||
|  |                             ), | ||||||
|  |                             Err(e) => Message::ErrorMessage(e.to_string()), | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     .into(); | ||||||
|  |                 } | ||||||
|  |                 Message::FetchedImage(url, handle) => { | ||||||
|  |                     view.images.insert(url, handle); | ||||||
|                 } |                 } | ||||||
|                 Message::SetVerification(v) => view.sas = v, |                 Message::SetVerification(v) => view.sas = v, | ||||||
|                 Message::VerificationAccept => { |                 Message::VerificationAccept => { | ||||||
|  | @ -965,13 +1112,13 @@ impl Application for Retrix { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn format_systime(time: std::time::SystemTime) -> String { | fn format_systime(time: std::time::SystemTime) -> String { | ||||||
|     let secs = time |     let offset = time::UtcOffset::try_current_local_offset().unwrap_or(time::UtcOffset::UTC); | ||||||
|         .duration_since(std::time::SystemTime::UNIX_EPOCH) |     let time = time::OffsetDateTime::from(time).to_offset(offset); | ||||||
|         .unwrap_or_default() |     let today = time::OffsetDateTime::now_utc().to_offset(offset).date(); | ||||||
|         .as_secs(); |     // Display
 | ||||||
|     format!( |     if time.date() == today { | ||||||
|         "{:02}:{:02}", |         time.format("%T") | ||||||
|         (secs % (60 * 60 * 24)) / (60 * 60), |     } else { | ||||||
|         (secs % (60 * 60)) / 60 |         time.format("%F %T") | ||||||
|     ) |     } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue