Improve login ux and session handling
This commit is contained in:
		
							parent
							
								
									a9c1d6ed01
								
							
						
					
					
						commit
						80f4ab6451
					
				
					 2 changed files with 119 additions and 77 deletions
				
			
		|  | @ -1,6 +1,5 @@ | ||||||
| use matrix_sdk::{ | use matrix_sdk::{ | ||||||
|     identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, Session, |     identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, SyncSettings, | ||||||
|     SyncSettings, |  | ||||||
| }; | }; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
|  | @ -8,23 +7,14 @@ pub type Error = anyhow::Error; | ||||||
| 
 | 
 | ||||||
| // Needed to be able to serialize `Session`s. Should be done with serde remote.
 | // Needed to be able to serialize `Session`s. Should be done with serde remote.
 | ||||||
| #[derive(Debug, Clone, Deserialize, Serialize)] | #[derive(Debug, Clone, Deserialize, Serialize)] | ||||||
| pub struct SessionWrapper { | pub struct Session { | ||||||
|     access_token: String, |     access_token: String, | ||||||
|     user_id: UserId, |     user_id: UserId, | ||||||
|     device_id: Box<DeviceId>, |     device_id: Box<DeviceId>, | ||||||
|  |     homeserver: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<SessionWrapper> for Session { | impl From<Session> for matrix_sdk::Session { | ||||||
|     fn from(s: SessionWrapper) -> Self { |  | ||||||
|         Self { |  | ||||||
|             access_token: s.access_token, |  | ||||||
|             user_id: s.user_id, |  | ||||||
|             device_id: s.device_id, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<Session> for SessionWrapper { |  | ||||||
|     fn from(s: Session) -> Self { |     fn from(s: Session) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             access_token: s.access_token, |             access_token: s.access_token, | ||||||
|  | @ -34,38 +24,51 @@ impl From<Session> for SessionWrapper { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Login with credentials, creating a new authentication session
 | ||||||
| pub async fn login( | pub async fn login( | ||||||
|     username: &str, |     username: &str, | ||||||
|     password: &str, |     password: &str, | ||||||
|     server: &str, |     server: &str, | ||||||
| ) -> Result<(Client, Session), Error> { | ) -> Result<(Client, Session), Error> { | ||||||
|     let url = Url::parse(server)?; |     let url = Url::parse(server)?; | ||||||
|     let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix")); |     let client = client(url)?; | ||||||
|     let client = Client::new_with_config(url, config)?; |  | ||||||
| 
 | 
 | ||||||
|     let session = match get_session()? { |  | ||||||
|         Some(session) => { |  | ||||||
|             client.restore_login(session.clone()).await?; |  | ||||||
|             session |  | ||||||
|         } |  | ||||||
|         None => { |  | ||||||
|     let response = client |     let response = client | ||||||
|                 .login(username, password, None, Some("retrix")) |         .login( | ||||||
|  |             username, | ||||||
|  |             password, | ||||||
|  |             None, | ||||||
|  |             Some(&format!("retrix@{}", hostname::get()?.to_string_lossy())), | ||||||
|  |         ) | ||||||
|         .await?; |         .await?; | ||||||
|     let session = Session { |     let session = Session { | ||||||
|         access_token: response.access_token, |         access_token: response.access_token, | ||||||
|         user_id: response.user_id, |         user_id: response.user_id, | ||||||
|         device_id: response.device_id, |         device_id: response.device_id, | ||||||
|  |         homeserver: server.to_owned(), | ||||||
|     }; |     }; | ||||||
|             write_session(session.clone())?; |     write_session(&session)?; | ||||||
|             session |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|     client.sync_once(SyncSettings::new()).await?; |     client.sync_once(SyncSettings::new()).await?; | ||||||
| 
 | 
 | ||||||
|     Ok((client, session)) |     Ok((client, session)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub async fn restore_login(session: Session) -> Result<(Client, Session), Error> { | ||||||
|  |     let url = Url::parse(&session.homeserver)?; | ||||||
|  |     let client = client(url)?; | ||||||
|  | 
 | ||||||
|  |     client.restore_login(session.clone().into()).await?; | ||||||
|  |     client.sync_once(SyncSettings::new()).await?; | ||||||
|  | 
 | ||||||
|  |     Ok((client, session)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Create a matrix client handler with the desired configuration
 | ||||||
|  | fn client(url: Url) -> Result<Client, matrix_sdk::Error> { | ||||||
|  |     let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix")); | ||||||
|  |     Client::new_with_config(url, config) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// File path to store session data in
 | /// File path to store session data in
 | ||||||
| fn session_path() -> std::path::PathBuf { | fn session_path() -> std::path::PathBuf { | ||||||
|     dirs::config_dir() |     dirs::config_dir() | ||||||
|  | @ -75,22 +78,19 @@ fn session_path() -> std::path::PathBuf { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Read session data from config file
 | /// Read session data from config file
 | ||||||
| fn get_session() -> Result<Option<Session>, Error> { | pub fn get_session() -> Result<Option<Session>, Error> { | ||||||
|     let path = session_path(); |     let path = session_path(); | ||||||
|     if !path.is_file() { |     if !path.is_file() { | ||||||
|         return Ok(None); |         return Ok(None); | ||||||
|     } |     } | ||||||
|     let session: SessionWrapper = toml::from_slice(&std::fs::read(path)?)?; |     let session: Session = toml::from_slice(&std::fs::read(path)?)?; | ||||||
|     Ok(Some(session.into())) |     Ok(Some(session)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Save session data to config file
 | /// Save session data to config file
 | ||||||
| fn write_session(session: Session) -> Result<(), Error> { | fn write_session(session: &Session) -> Result<(), Error> { | ||||||
|     let session: SessionWrapper = session.into(); |  | ||||||
|     let path = session_path(); |  | ||||||
| 
 |  | ||||||
|     let serialized = toml::to_string(&session)?; |     let serialized = toml::to_string(&session)?; | ||||||
|     std::fs::write(path, serialized)?; |     std::fs::write(session_path(), serialized)?; | ||||||
| 
 | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										114
									
								
								src/ui.rs
									
										
									
									
									
								
							
							
						
						
									
										114
									
								
								src/ui.rs
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| use iced::{ | use iced::{ | ||||||
|     text_input::{self, TextInput}, |     text_input::{self, TextInput}, | ||||||
|     Application, Button, Column, Command, Container, Element, Length, Row, Text, |     Application, Button, Column, Command, Container, Element, Length, Scrollable, Text, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::matrix; | use crate::matrix; | ||||||
|  | @ -18,35 +18,19 @@ pub enum Retrix { | ||||||
|         server: String, |         server: String, | ||||||
|         error: Option<String>, |         error: Option<String>, | ||||||
|     }, |     }, | ||||||
|  |     AwaitLogin, | ||||||
|     LoggedIn { |     LoggedIn { | ||||||
|         client: matrix_sdk::Client, |         client: matrix_sdk::Client, | ||||||
|         session: matrix_sdk::Session, |         session: matrix::Session, | ||||||
| 
 | 
 | ||||||
|         rooms: Vec<matrix_sdk::Room>, |         rooms: Vec<String>, | ||||||
|  |         room_scroll: iced::scrollable::State, | ||||||
|     }, |     }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | impl Retrix { | ||||||
| pub enum Message { |     pub fn new_prompt() -> Retrix { | ||||||
|     // Login form messages
 |         Retrix::Prompt { | ||||||
|     SetUser(String), |  | ||||||
|     SetPassword(String), |  | ||||||
|     SetServer(String), |  | ||||||
|     Login, |  | ||||||
|     LoggedIn(matrix_sdk::Client, matrix_sdk::Session), |  | ||||||
|     SetError(String), |  | ||||||
| 
 |  | ||||||
|     // Main state messages
 |  | ||||||
|     ResetRooms(Vec<matrix_sdk::Room>), |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Application for Retrix { |  | ||||||
|     type Message = Message; |  | ||||||
|     type Executor = iced::executor::Default; |  | ||||||
|     type Flags = (); |  | ||||||
| 
 |  | ||||||
|     fn new(_flags: ()) -> (Self, Command<Self::Message>) { |  | ||||||
|         let app = Retrix::Prompt { |  | ||||||
|             user_input: text_input::State::new(), |             user_input: text_input::State::new(), | ||||||
|             password_input: text_input::State::new(), |             password_input: text_input::State::new(), | ||||||
|             server_input: text_input::State::new(), |             server_input: text_input::State::new(), | ||||||
|  | @ -56,8 +40,44 @@ impl Application for Retrix { | ||||||
|             password: String::new(), |             password: String::new(), | ||||||
|             server: String::new(), |             server: String::new(), | ||||||
|             error: None, |             error: None, | ||||||
|         }; |         } | ||||||
|         (app, Command::none()) |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub enum Message { | ||||||
|  |     // Login form messages
 | ||||||
|  |     SetUser(String), | ||||||
|  |     SetPassword(String), | ||||||
|  |     SetServer(String), | ||||||
|  |     Login, | ||||||
|  |     LoggedIn(matrix_sdk::Client, matrix::Session), | ||||||
|  |     LoginFailed(String), | ||||||
|  | 
 | ||||||
|  |     // Main state messages
 | ||||||
|  |     ResetRooms(Vec<String>), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Application for Retrix { | ||||||
|  |     type Message = Message; | ||||||
|  |     type Executor = iced::executor::Default; | ||||||
|  |     type Flags = (); | ||||||
|  | 
 | ||||||
|  |     fn new(_flags: ()) -> (Self, Command<Self::Message>) { | ||||||
|  |         // Skip login prompt if we have a session saved
 | ||||||
|  |         match matrix::get_session().ok().flatten() { | ||||||
|  |             Some(session) => { | ||||||
|  |                 let command = Command::perform( | ||||||
|  |                     async move { matrix::restore_login(session).await }, | ||||||
|  |                     |result| match result { | ||||||
|  |                         Ok((s, c)) => Message::LoggedIn(s, c), | ||||||
|  |                         Err(e) => Message::LoginFailed(e.to_string()), | ||||||
|  |                     }, | ||||||
|  |                 ); | ||||||
|  |                 (Retrix::AwaitLogin, command) | ||||||
|  |             } | ||||||
|  |             None => (Retrix::new_prompt(), Command::none()), | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn title(&self) -> String { |     fn title(&self) -> String { | ||||||
|  | @ -70,38 +90,47 @@ impl Application for Retrix { | ||||||
|                 ref mut user, |                 ref mut user, | ||||||
|                 ref mut password, |                 ref mut password, | ||||||
|                 ref mut server, |                 ref mut server, | ||||||
|                 ref mut error, |  | ||||||
|                 .. |                 .. | ||||||
|             } => match message { |             } => match message { | ||||||
|                 Message::SetUser(u) => *user = u, |                 Message::SetUser(u) => *user = u, | ||||||
|                 Message::SetPassword(p) => *password = p, |                 Message::SetPassword(p) => *password = p, | ||||||
|                 Message::SetServer(s) => *server = s, |                 Message::SetServer(s) => *server = s, | ||||||
|                 Message::SetError(e) => *error = Some(e), |  | ||||||
|                 Message::Login => { |                 Message::Login => { | ||||||
|                     let user = user.clone(); |                     let user = user.clone(); | ||||||
|                     let password = password.clone(); |                     let password = password.clone(); | ||||||
|                     let server = server.clone(); |                     let server = server.clone(); | ||||||
|  |                     *self = Retrix::AwaitLogin; | ||||||
|                     return Command::perform( |                     return Command::perform( | ||||||
|                         async move { matrix::login(&user, &password, &server).await }, |                         async move { matrix::login(&user, &password, &server).await }, | ||||||
|                         |result| match result { |                         |result| match result { | ||||||
|                             Ok((c, r)) => Message::LoggedIn(c, r), |                             Ok((c, r)) => Message::LoggedIn(c, r), | ||||||
|                             Err(e) => Message::SetError(e.to_string()), |                             Err(e) => Message::LoginFailed(e.to_string()), | ||||||
|                         }, |                         }, | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|  |                 _ => (), | ||||||
|  |             }, | ||||||
|  |             Retrix::AwaitLogin => match message { | ||||||
|  |                 Message::LoginFailed(e) => { | ||||||
|  |                     *self = Retrix::new_prompt(); | ||||||
|  |                     if let Retrix::Prompt { ref mut error, .. } = *self { | ||||||
|  |                         *error = Some(e); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|                 Message::LoggedIn(client, session) => { |                 Message::LoggedIn(client, session) => { | ||||||
|                     *self = Retrix::LoggedIn { |                     *self = Retrix::LoggedIn { | ||||||
|                         client: client.clone(), |                         client: client.clone(), | ||||||
|                         session, |                         session, | ||||||
|                         rooms: Vec::new(), |                         rooms: Vec::new(), | ||||||
|  |                         room_scroll: Default::default(), | ||||||
|                     }; |                     }; | ||||||
|                     let client = client.clone(); |                     let client = client.clone(); | ||||||
|                     Command::perform( |                     return Command::perform( | ||||||
|                         async move { |                         async move { | ||||||
|                             let mut list = Vec::new(); |                             let mut list = Vec::new(); | ||||||
|                             for (id, room) in client.joined_rooms().read().await.iter() { |                             for (_, room) in client.joined_rooms().read().await.iter() { | ||||||
|                                 let room = room.read().await; |                                 let name = room.read().await.display_name(); | ||||||
|                                 list.push(room.clone()); |                                 list.push(name); | ||||||
|                             } |                             } | ||||||
|                             list |                             list | ||||||
|                         }, |                         }, | ||||||
|  | @ -165,11 +194,24 @@ impl Application for Retrix { | ||||||
|                     .height(iced::Length::Fill) |                     .height(iced::Length::Fill) | ||||||
|                     .into() |                     .into() | ||||||
|             } |             } | ||||||
|             Retrix::LoggedIn { ref rooms, .. } => { |             Retrix::AwaitLogin => Container::new(Text::new("Logging in...")) | ||||||
|  |                 .center_x() | ||||||
|  |                 .center_y() | ||||||
|  |                 .width(Length::Fill) | ||||||
|  |                 .height(Length::Fill) | ||||||
|  |                 .into(), | ||||||
|  |             Retrix::LoggedIn { | ||||||
|  |                 ref rooms, | ||||||
|  |                 ref mut room_scroll, | ||||||
|  |                 .. | ||||||
|  |             } => { | ||||||
|                 //let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
 |                 //let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
 | ||||||
|                 let mut room_col = Column::new().width(400.into()).height(Length::Fill); |                 let mut room_col = Scrollable::new(room_scroll) | ||||||
|  |                     .width(400.into()) | ||||||
|  |                     .height(Length::Fill) | ||||||
|  |                     .spacing(15); | ||||||
|                 for room in rooms { |                 for room in rooms { | ||||||
|                     room_col = room_col.push(Text::new(room.display_name())); |                     room_col = room_col.push(Text::new(room)); | ||||||
|                 } |                 } | ||||||
|                 room_col.into() |                 room_col.into() | ||||||
|                 //root_row = root_row.push(room_col);
 |                 //root_row = root_row.push(room_col);
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue