Basic login screen

main
Amanda Graven 2021-10-16 21:50:22 +02:00
commit 245fe8e6f5
Signed by: amanda
GPG Key ID: 45C461CDC9286390
8 changed files with 4152 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

3833
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "egui-test"
version = "0.1.0"
edition = "2018"
resolver = "2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
eframe = { version = "0.14", features = ["persistence", "http"] }
futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "*", features = ["full"] }
url = { version = "2.2", features = ["serde"] }
[dependencies.matrix-sdk]
git = "https://github.com/matrix-org/matrix-rust-sdk/"
rev = "9e1024f"
default-features = false
features = ["encryption", "qrcode", "sled_cryptostore", "sled_state_store", "require_auth_for_profile_requests", "rustls-tls"]

56
src/main.rs Normal file
View File

@ -0,0 +1,56 @@
pub mod matrix;
pub mod ui;
use eframe::NativeOptions;
#[cfg(not(target_arch = "wasm32"))]
fn main() {
let app = ui::App::default();
let options = NativeOptions::default();
eframe::run_native(Box::new(app), options);
}
/*#[derive(Clone, Debug, Default)]
struct Login {
username: String,
password: String,
}
impl epi::App for Login {
fn name(&self) -> &str {
"retrix"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.style_mut().body_text_style = egui::TextStyle::Button;
ui.add_space(ui.available_height() / 3.0);
ui.vertical_centered(|ui| {
//ui.style_mut() .visuals .widgets .noninteractive .bg_stroke .color = egui::Color32::TRANSPARENT;
ui.group(|ui| {
//ui.reset_style();
ui.set_max_width(300.0);
ui.vertical(|ui| {
ui.heading("Test");
ui.label("Username");
ui.text_edit_singleline(&mut self.username);
ui.label("Password:");
ui.text_edit_singleline(&mut self.password);
})
})
});
/*let mut ui = ui.child_ui(
egui::Rect::from_center_size(
(ui.available_width() / 2.0, ui.available_height() / 2.0).into(),
(300.0, 500.0).into(),
),
egui::Layout::top_down(egui::Align::Min),
);
ui.heading("Test");
ui.label("Username");
ui.text_edit_singleline(&mut self.username);
ui.label("Password:");
ui.text_edit_singleline(&mut self.password);*/
});
}
}*/

96
src/matrix.rs Normal file
View File

@ -0,0 +1,96 @@
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 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 rt = tokio::runtime::Runtime::new().unwrap();
let response = rt.block_on(client.login(user, password, None, None))?;
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)
}

59
src/ui.rs Normal file
View File

@ -0,0 +1,59 @@
use eframe::{egui, epi};
use crate::matrix;
pub mod login;
pub mod session;
/// Application state
#[derive(Debug, Default)]
pub struct App {
view: View,
}
impl epi::App for App {
fn name(&self) -> &str {
"retrix"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
match self.view {
View::Login(ref mut login) => {
if login.update(ctx) {
let client =
match matrix::login(&login.homeserver, &login.username, &login.password) {
Ok(client) => client,
Err(e) => {
login.error = Some(e.to_string());
return;
}
};
self.view = View::Main(session::App::with_client(client.clone()));
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(client.sync(Default::default()))
});
}
}
View::Main(ref mut view) => view.update(ctx),
};
}
}
/// Which view is currectly open
#[derive(Debug)]
pub enum View {
Login(login::Login),
Main(session::App),
}
impl Default for View {
fn default() -> Self {
View::Login(login::Login::default())
}
}
#[derive(Debug, Clone, Copy, Hash)]
pub enum Id {
RoomPanel,
}

44
src/ui/login.rs Normal file
View File

@ -0,0 +1,44 @@
use eframe::egui::{self, Color32, TextEdit};
#[derive(Clone, Debug, Default)]
pub struct Login {
pub homeserver: String,
pub username: String,
pub password: String,
pub error: Option<String>,
}
impl Login {
pub fn update(&mut self, ctx: &egui::CtxRef) -> bool {
let mut update = false;
egui::CentralPanel::default().show(ctx, |ui| {
ui.add_space(ui.available_height() / 3.0);
ui.vertical_centered(|ui| {
//ui.style_mut() .visuals .widgets .noninteractive .bg_stroke .color = egui::Color32::TRANSPARENT;
ui.group(|ui| {
//ui.reset_style();
if let Some(ref error) = self.error {
ui.colored_label(Color32::from_rgb(255, 0, 0), error);
}
ui.set_max_width(300.0);
ui.vertical(|ui| {
ui.heading("Log in");
ui.label("Homeserver:");
ui.add(
TextEdit::singleline(&mut self.homeserver)
.hint_text("https://example.net"),
);
ui.label("Username:");
ui.add(TextEdit::singleline(&mut self.username).hint_text("alice"));
ui.label("Password:");
ui.add(TextEdit::singleline(&mut self.password).password(true));
if ui.button("Log in").clicked() {
update = true;
}
})
})
});
});
update
}
}

43
src/ui/session.rs Normal file
View File

@ -0,0 +1,43 @@
use eframe::egui::{self, Sense};
use futures::executor::block_on;
use matrix_sdk::Client;
use super::Id;
/// Logged in application state
#[derive(Debug)]
pub struct App {
client: matrix_sdk::Client,
selected_room: Option<matrix_sdk::ruma::identifiers::RoomId>,
}
impl App {
pub fn with_client(client: Client) -> Self {
Self {
client,
selected_room: None,
}
}
pub fn update(&mut self, ctx: &egui::CtxRef) {
egui::SidePanel::left(Id::RoomPanel)
.max_width(800.0)
.show(ctx, |ui| {
ui.add(egui::Label::new("Joined").strong());
for room in self.client.joined_rooms() {
let response = ui.group(|ui| {
if let Some(name) = room.name() {
ui.label(name.to_string());
}
if let Some(alias) = room.canonical_alias() {
ui.label(alias.to_string());
}
});
let response = response.response.interact(Sense::click());
if response.clicked() {
self.selected_room = Some(room.room_id().clone());
}
}
});
}
}