Finish login

This commit is contained in:
Amanda Graven 2021-10-16 20:37:03 +02:00
parent eed07706df
commit 22981ea573
Signed by: amanda
GPG key ID: 45C461CDC9286390
8 changed files with 991 additions and 1039 deletions

1788
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@ name = "retrix"
authors = ["Amanda Graven <amanda@amandag.net>"] authors = ["Amanda Graven <amanda@amandag.net>"]
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2018"
resolver = "2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -10,14 +11,15 @@ edition = "2018"
directories = "3.0" directories = "3.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
url = { version = "2.2", features = ["serde"] }
[dependencies.iced] [dependencies.iced]
git = "https://github.com/hecrj/iced" git = "https://github.com/hecrj/iced"
rev = "a08e4eb" rev = "378a135"
features = ["image", "svg", "debug"] features = ["image", "svg", "debug", "tokio"]
[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 = "1fd1570" rev = "9e1024f"
default-features = false default-features = false
features = ["encryption", "sled_state_store", "sled_cryptostore", "rustls-tls", "require_auth_for_profile_requests"] features = ["encryption", "sled_state_store", "sled_cryptostore", "rustls-tls", "require_auth_for_profile_requests"]

View file

@ -5,8 +5,7 @@
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,
unused_extern_crates, unused_extern_crates,
unused_allocation, unused_allocation
unused_qualifications
)] )]
use config::Config; use config::Config;
@ -18,6 +17,8 @@ use crate::{config::Session, ui::Retrix};
extern crate directories as dirs; extern crate directories as dirs;
pub mod config; pub mod config;
pub mod matrix;
pub mod style;
pub mod ui; pub mod ui;
fn main() { fn main() {

90
src/matrix.rs Normal file
View file

@ -0,0 +1,90 @@
use std::path::{Path, PathBuf};
use matrix_sdk::{
config::ClientConfig,
reqwest::Url,
ruma::{DeviceIdBox, UserId},
Client,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
/// The homeserver URL
pub homeserver: Url,
/// Access token for authentication
pub access_token: String,
/// The user's mxid.
pub user_id: UserId,
/// The user's device ID.
pub device_id: DeviceIdBox,
}
/// Create a matrix client and log it in to the server at the given URL with the
/// given credentials.
pub async fn login(url: &str, user: &str, password: &str) -> Result<Client, LoginError> {
let url: Url =
if !url.contains("://") { format!("https://{}", url).parse() } else { url.parse() }?;
let client = Client::new_with_config(url.clone(), config()?)?;
let response = client.login(user, password, None, None).await?;
let session = Session {
homeserver: url,
access_token: response.access_token,
user_id: response.user_id,
device_id: response.device_id,
};
Ok(client)
}
/// Errors that can happen when logging in
#[derive(Debug)]
pub enum LoginError {
/// Invalid URL
Url(url::ParseError),
/// Matrix SDK error
Sdk(matrix_sdk::Error),
/// I/O error
Io(std::io::Error),
}
impl std::fmt::Display for LoginError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LoginError::Url(_) => write!(f, "Invalid homeserver address"),
LoginError::Sdk(e) => write!(f, "{}", e),
LoginError::Io(e) => write!(f, "Filesystem error: {}", e),
}
}
}
impl std::error::Error for LoginError {}
impl From<url::ParseError> for LoginError {
fn from(e: url::ParseError) -> Self {
LoginError::Url(e)
}
}
impl From<matrix_sdk::Error> for LoginError {
fn from(e: matrix_sdk::Error) -> Self {
LoginError::Sdk(e)
}
}
impl From<std::io::Error> for LoginError {
fn from(e: std::io::Error) -> Self {
LoginError::Io(e)
}
}
/// Configuration for `Clients`.
fn config() -> Result<ClientConfig, std::io::Error> {
Ok(ClientConfig::new().store_path(&path()?))
}
/// The path the the sdk store should be put in.
fn path() -> Result<PathBuf, std::io::Error> {
let path = Path::new(&std::env::var_os("HOME").unwrap()).join(".config").join("retrix");
std::fs::create_dir_all(&path)?;
Ok(path)
}

24
src/style.rs Normal file
View file

@ -0,0 +1,24 @@
//! Style definitions for various elements
/// Style definitions for [`iced::Container`]
pub mod container {
use iced::{
container::{Style, StyleSheet},
Color,
};
#[derive(Debug)]
/// Style for a container displaying an error message
pub struct Error;
impl StyleSheet for Error {
fn style(&self) -> Style {
iced::container::Style {
background: Color::from_rgb(1.0, 0.0, 0.0).into(),
text_color: Some(Color::from_rgb(1.0, 1.0, 1.0)),
border_radius: 2.0,
..Default::default()
}
}
}
}

View file

@ -5,6 +5,7 @@ use iced::Command;
use crate::config; use crate::config;
pub mod login; pub mod login;
pub mod session;
/// Data for the running application /// Data for the running application
#[derive(Debug)] #[derive(Debug)]
@ -20,6 +21,8 @@ pub struct Retrix {
pub enum View { pub enum View {
/// The login prompt /// The login prompt
Login(login::Login), Login(login::Login),
/// The main view
Main(session::View),
} }
/// A message notifying application state should change /// A message notifying application state should change
@ -36,7 +39,7 @@ pub enum Message {
pub struct Flags { pub struct Flags {
/// The application configuration /// The application configuration
pub config: config::Config, pub config: config::Config,
/// The session data if we've loggen in /// The session data if we've logged in
pub session: Option<config::Session>, pub session: Option<config::Session>,
} }
@ -54,12 +57,12 @@ impl iced::Application for Retrix {
String::from("Retrix") String::from("Retrix")
} }
fn update( fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
&mut self,
message: Self::Message,
_clipboard: &mut iced::Clipboard,
) -> Command<Self::Message> {
match (&mut self.view, message) { match (&mut self.view, message) {
(_, Message::Login(login::Message::LoggedIn(client))) => {
self.view = View::Main(session::View::with_client(client));
Command::none()
}
(View::Login(ref mut view), Message::Login(message)) => view.update(message), (View::Login(ref mut view), Message::Login(message)) => view.update(message),
_ => { _ => {
eprint!("WARN: Received a message for an inactive view"); eprint!("WARN: Received a message for an inactive view");
@ -71,6 +74,7 @@ impl iced::Application for Retrix {
fn view(&mut self) -> iced::Element<'_, Self::Message> { fn view(&mut self) -> iced::Element<'_, Self::Message> {
match self.view { match self.view {
View::Login(ref mut login) => login.view(), View::Login(ref mut login) => login.view(),
View::Main(ref mut view) => view.view(),
} }
} }

View file

@ -5,7 +5,8 @@ use iced::{
Button, Column, Command, Container, Element, Length, Space, Text, TextInput, Toggler, Button, Column, Command, Container, Element, Length, Space, Text, TextInput, Toggler,
}; };
use self::Message::{InputHomeserver, InputPassword, InputUser, LoginFailed, TogglePassword}; use self::Message::*;
use crate::{matrix, style};
/// Data for the login prompt /// Data for the login prompt
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -20,6 +21,8 @@ pub struct Login {
homeserver: String, homeserver: String,
/// Whether we're waiting for a response to a login attempt /// Whether we're waiting for a response to a login attempt
waiting: bool, waiting: bool,
/// The text of an error message
error: Option<String>,
/// Widget state /// Widget state
state: State, state: State,
@ -49,10 +52,14 @@ pub enum Message {
InputHomeserver(String), InputHomeserver(String),
/// The "show password" toggle has been switched. /// The "show password" toggle has been switched.
TogglePassword(bool), TogglePassword(bool),
/// Triggers login /// Triggers login.
Login, Login,
/// A login attempt failed /// A login attempt failed.
LoginFailed(String), LoginFailed(String),
/// Login completed.
LoggedIn(matrix_sdk::Client),
/// Hide the error message.
ResetError,
} }
impl Login { impl Login {
@ -63,23 +70,41 @@ impl Login {
InputPassword(input) => self.password = input, InputPassword(input) => self.password = input,
InputHomeserver(input) => self.homeserver = input, InputHomeserver(input) => self.homeserver = input,
TogglePassword(toggle) => self.show_password = toggle, TogglePassword(toggle) => self.show_password = toggle,
Message::Login => { Login => {
self.waiting = true; self.waiting = true;
let command = async { let homeserver = self.homeserver.clone();
std::thread::sleep_ms(1000); let user = self.user.clone();
super::Message::from(LoginFailed(String::from("Not implemented :("))) let password = self.password.clone();
let command = async move {
let client = match matrix::login(&homeserver, &user, &password).await {
Ok(client) => client,
Err(e) => return LoginFailed(e.to_string()),
};
LoggedIn(client)
}; };
return command.into(); return Command::perform(command, super::Message::from);
} }
LoginFailed(error) => { LoginFailed(error) => {
self.waiting = false; self.waiting = false;
self.error = Some(error);
} }
LoggedIn(_) => (),
ResetError => self.error = None,
}; };
Command::none() Command::none()
} }
/// Generate widgets for this view /// Generate widgets for this view
pub fn view(&mut self) -> Element<super::Message> { pub fn view(&mut self) -> Element<super::Message> {
let error_message: iced::Element<_> = match self.error {
Some(ref error) => Container::new(Text::new(error.clone()))
.center_x()
.width(Length::Fill)
.padding(5)
.style(style::container::Error)
.into(),
None => Space::new(0.into(), 0.into()).into(),
};
let user_input = let user_input =
TextInput::new(&mut self.state.user, "alice", &self.user, |i| InputUser(i).into()) TextInput::new(&mut self.state.user, "alice", &self.user, |i| InputUser(i).into())
.padding(5); .padding(5);
@ -110,7 +135,7 @@ impl Login {
let login_button = Button::new( let login_button = Button::new(
&mut self.state.login, &mut self.state.login,
Text::new("Log in") Text::new("Log in")
.horizontal_alignment(iced::HorizontalAlignment::Center) .horizontal_alignment(iced::alignment::Horizontal::Center)
.width(Length::Fill), .width(Length::Fill),
) )
.on_press(Message::Login.into()) .on_press(Message::Login.into())
@ -118,6 +143,7 @@ impl Login {
let mut column = Column::new() let mut column = Column::new()
.width(500.into()) .width(500.into())
.push(error_message)
.push(Text::new("User name")) .push(Text::new("User name"))
.push(user_input) .push(user_input)
.push(Space::with_height(10.into())) .push(Space::with_height(10.into()))
@ -130,7 +156,7 @@ impl Login {
.push(login_button); .push(login_button);
if self.waiting { if self.waiting {
column = column.push(Text::new("Loggin in")); column = column.push(Text::new("Logging in"));
} }
Container::new(column).center_x().center_y().width(Length::Fill).height(Length::Fill).into() Container::new(column).center_x().center_y().width(Length::Fill).height(Length::Fill).into()

53
src/ui/session.rs Normal file
View file

@ -0,0 +1,53 @@
//! View for a logged in session
use std::collections::HashMap;
use iced::{Element, Row, Space};
use matrix_sdk::{ruma::RoomId, Client};
/// The main view, for a logged in session
#[derive(Clone, Debug)]
pub struct View {
client: matrix_sdk::Client,
/// List of known rooms
room_list: RoomList,
/// Widget state
state: State,
}
/// Widget state.
#[derive(Clone, Debug, Default)]
struct State {}
/// The list of rooms
#[derive(Clone, Debug, Default)]
pub struct RoomList {
names: HashMap<RoomId, String>,
}
impl RoomList {
fn view(&mut self) -> Element<super::Message> {
Space::new(0.into(), 0.into()).into()
}
}
/// State change notification
#[derive(Debug)]
pub enum Message {
/// The name for a room has been recalculated.
RoomName(RoomId, String),
}
impl View {
/// Create a new view.
pub fn with_client(client: Client) -> Self {
Self { client, room_list: RoomList::default(), state: State::default() }
}
/// Generate widgets
pub fn view(&mut self) -> Element<super::Message> {
Row::new().push(self.room_list.view()).into()
}
}