Finish login

This commit is contained in:
Amanda Graven 2021-10-16 20:37:03 +02:00
parent eed07706df
commit 22981ea573
No known key found for this signature in database
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>"]
version = "0.1.0"
edition = "2018"
resolver = "2"
# 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"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = { version = "2.2", features = ["serde"] }
[dependencies.iced]
git = "https://github.com/hecrj/iced"
rev = "a08e4eb"
features = ["image", "svg", "debug"]
rev = "378a135"
features = ["image", "svg", "debug", "tokio"]
[dependencies.matrix-sdk]
git = "https://github.com/matrix-org/matrix-rust-sdk"
rev = "1fd1570"
rev = "9e1024f"
default-features = false
features = ["encryption", "sled_state_store", "sled_cryptostore", "rustls-tls", "require_auth_for_profile_requests"]

View File

@ -5,8 +5,7 @@
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_allocation,
unused_qualifications
unused_allocation
)]
use config::Config;
@ -18,6 +17,8 @@ use crate::{config::Session, ui::Retrix};
extern crate directories as dirs;
pub mod config;
pub mod matrix;
pub mod style;
pub mod ui;
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;
pub mod login;
pub mod session;
/// Data for the running application
#[derive(Debug)]
@ -20,6 +21,8 @@ pub struct Retrix {
pub enum View {
/// The login prompt
Login(login::Login),
/// The main view
Main(session::View),
}
/// A message notifying application state should change
@ -36,7 +39,7 @@ pub enum Message {
pub struct Flags {
/// The application configuration
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>,
}
@ -54,12 +57,12 @@ impl iced::Application for Retrix {
String::from("Retrix")
}
fn update(
&mut self,
message: Self::Message,
_clipboard: &mut iced::Clipboard,
) -> Command<Self::Message> {
fn update(&mut self, message: Self::Message) -> Command<Self::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),
_ => {
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> {
match self.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,
};
use self::Message::{InputHomeserver, InputPassword, InputUser, LoginFailed, TogglePassword};
use self::Message::*;
use crate::{matrix, style};
/// Data for the login prompt
#[derive(Debug, Default)]
@ -20,6 +21,8 @@ pub struct Login {
homeserver: String,
/// Whether we're waiting for a response to a login attempt
waiting: bool,
/// The text of an error message
error: Option<String>,
/// Widget state
state: State,
@ -49,10 +52,14 @@ pub enum Message {
InputHomeserver(String),
/// The "show password" toggle has been switched.
TogglePassword(bool),
/// Triggers login
/// Triggers login.
Login,
/// A login attempt failed
/// A login attempt failed.
LoginFailed(String),
/// Login completed.
LoggedIn(matrix_sdk::Client),
/// Hide the error message.
ResetError,
}
impl Login {
@ -63,23 +70,41 @@ impl Login {
InputPassword(input) => self.password = input,
InputHomeserver(input) => self.homeserver = input,
TogglePassword(toggle) => self.show_password = toggle,
Message::Login => {
Login => {
self.waiting = true;
let command = async {
std::thread::sleep_ms(1000);
super::Message::from(LoginFailed(String::from("Not implemented :(")))
let homeserver = self.homeserver.clone();
let user = self.user.clone();
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) => {
self.waiting = false;
self.error = Some(error);
}
LoggedIn(_) => (),
ResetError => self.error = None,
};
Command::none()
}
/// Generate widgets for this view
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 =
TextInput::new(&mut self.state.user, "alice", &self.user, |i| InputUser(i).into())
.padding(5);
@ -110,7 +135,7 @@ impl Login {
let login_button = Button::new(
&mut self.state.login,
Text::new("Log in")
.horizontal_alignment(iced::HorizontalAlignment::Center)
.horizontal_alignment(iced::alignment::Horizontal::Center)
.width(Length::Fill),
)
.on_press(Message::Login.into())
@ -118,6 +143,7 @@ impl Login {
let mut column = Column::new()
.width(500.into())
.push(error_message)
.push(Text::new("User name"))
.push(user_input)
.push(Space::with_height(10.into()))
@ -130,7 +156,7 @@ impl Login {
.push(login_button);
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()

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()
}
}