Basic login screen
This commit is contained in:
commit
245fe8e6f5
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
3833
Cargo.lock
generated
Normal file
3833
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
20
Cargo.toml
Normal file
20
Cargo.toml
Normal 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
56
src/main.rs
Normal 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
96
src/matrix.rs
Normal 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
59
src/ui.rs
Normal 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
44
src/ui/login.rs
Normal 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
43
src/ui/session.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue