From 80f4ab64515ae76b941d6d708482b3d1e0a08e77 Mon Sep 17 00:00:00 2001 From: Amanda Graven Date: Tue, 24 Nov 2020 21:49:06 +0100 Subject: [PATCH] Improve login ux and session handling --- src/matrix.rs | 82 ++++++++++++++++++------------------ src/ui.rs | 114 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 119 insertions(+), 77 deletions(-) diff --git a/src/matrix.rs b/src/matrix.rs index a2deb89..408a7f6 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -1,6 +1,5 @@ use matrix_sdk::{ - identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, Session, - SyncSettings, + identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, SyncSettings, }; 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. #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct SessionWrapper { +pub struct Session { access_token: String, user_id: UserId, device_id: Box, + homeserver: String, } -impl From for Session { - fn from(s: SessionWrapper) -> Self { - Self { - access_token: s.access_token, - user_id: s.user_id, - device_id: s.device_id, - } - } -} - -impl From for SessionWrapper { +impl From for matrix_sdk::Session { fn from(s: Session) -> Self { Self { access_token: s.access_token, @@ -34,38 +24,51 @@ impl From for SessionWrapper { } } +/// Login with credentials, creating a new authentication session pub async fn login( username: &str, password: &str, server: &str, ) -> Result<(Client, Session), Error> { let url = Url::parse(server)?; - let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix")); - let client = Client::new_with_config(url, config)?; + let client = client(url)?; - let session = match get_session()? { - Some(session) => { - client.restore_login(session.clone()).await?; - session - } - None => { - let response = client - .login(username, password, None, Some("retrix")) - .await?; - let session = Session { - access_token: response.access_token, - user_id: response.user_id, - device_id: response.device_id, - }; - write_session(session.clone())?; - session - } + let response = client + .login( + username, + password, + None, + Some(&format!("retrix@{}", hostname::get()?.to_string_lossy())), + ) + .await?; + let session = Session { + access_token: response.access_token, + user_id: response.user_id, + device_id: response.device_id, + homeserver: server.to_owned(), }; + write_session(&session)?; client.sync_once(SyncSettings::new()).await?; 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 { + 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 fn session_path() -> std::path::PathBuf { dirs::config_dir() @@ -75,22 +78,19 @@ fn session_path() -> std::path::PathBuf { } /// Read session data from config file -fn get_session() -> Result, Error> { +pub fn get_session() -> Result, Error> { let path = session_path(); if !path.is_file() { return Ok(None); } - let session: SessionWrapper = toml::from_slice(&std::fs::read(path)?)?; - Ok(Some(session.into())) + let session: Session = toml::from_slice(&std::fs::read(path)?)?; + Ok(Some(session)) } /// Save session data to config file -fn write_session(session: Session) -> Result<(), Error> { - let session: SessionWrapper = session.into(); - let path = session_path(); - +fn write_session(session: &Session) -> Result<(), Error> { let serialized = toml::to_string(&session)?; - std::fs::write(path, serialized)?; + std::fs::write(session_path(), serialized)?; Ok(()) } diff --git a/src/ui.rs b/src/ui.rs index afd5be3..d0bcb77 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,6 +1,6 @@ use iced::{ 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; @@ -18,35 +18,19 @@ pub enum Retrix { server: String, error: Option, }, + AwaitLogin, LoggedIn { client: matrix_sdk::Client, - session: matrix_sdk::Session, + session: matrix::Session, - rooms: Vec, + rooms: Vec, + room_scroll: iced::scrollable::State, }, } -#[derive(Debug, Clone)] -pub enum Message { - // Login form messages - SetUser(String), - SetPassword(String), - SetServer(String), - Login, - LoggedIn(matrix_sdk::Client, matrix_sdk::Session), - SetError(String), - - // Main state messages - ResetRooms(Vec), -} - -impl Application for Retrix { - type Message = Message; - type Executor = iced::executor::Default; - type Flags = (); - - fn new(_flags: ()) -> (Self, Command) { - let app = Retrix::Prompt { +impl Retrix { + pub fn new_prompt() -> Retrix { + Retrix::Prompt { user_input: text_input::State::new(), password_input: text_input::State::new(), server_input: text_input::State::new(), @@ -56,8 +40,44 @@ impl Application for Retrix { password: String::new(), server: String::new(), 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), +} + +impl Application for Retrix { + type Message = Message; + type Executor = iced::executor::Default; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command) { + // 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 { @@ -70,38 +90,47 @@ impl Application for Retrix { ref mut user, ref mut password, ref mut server, - ref mut error, .. } => match message { Message::SetUser(u) => *user = u, Message::SetPassword(p) => *password = p, Message::SetServer(s) => *server = s, - Message::SetError(e) => *error = Some(e), Message::Login => { let user = user.clone(); let password = password.clone(); let server = server.clone(); + *self = Retrix::AwaitLogin; return Command::perform( async move { matrix::login(&user, &password, &server).await }, |result| match result { 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) => { *self = Retrix::LoggedIn { client: client.clone(), session, rooms: Vec::new(), + room_scroll: Default::default(), }; let client = client.clone(); - Command::perform( + return Command::perform( async move { let mut list = Vec::new(); - for (id, room) in client.joined_rooms().read().await.iter() { - let room = room.read().await; - list.push(room.clone()); + for (_, room) in client.joined_rooms().read().await.iter() { + let name = room.read().await.display_name(); + list.push(name); } list }, @@ -165,11 +194,24 @@ impl Application for Retrix { .height(iced::Length::Fill) .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 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 { - room_col = room_col.push(Text::new(room.display_name())); + room_col = room_col.push(Text::new(room)); } room_col.into() //root_row = root_row.push(room_col);