Add support for logging in to an account
This commit is contained in:
parent
5bd3491b00
commit
2d845708c4
|
@ -1,10 +1,10 @@
|
||||||
use lemmy_api_common::{person::{LoginResponse, Login}, sensitive::Sensitive};
|
use lemmy_api_common::{person::Login, sensitive::Sensitive};
|
||||||
|
|
||||||
pub fn login(username_or_email: String, password: String) -> std::result::Result<LoginResponse, reqwest::Error> {
|
pub fn login(username_or_email: String, password: String) -> std::result::Result<lemmy_api_common::person::LoginResponse, reqwest::Error> {
|
||||||
let params = Login {
|
let params = Login {
|
||||||
username_or_email: Sensitive::new(username_or_email),
|
username_or_email: Sensitive::new(username_or_email),
|
||||||
password: Sensitive::new(password)
|
password: Sensitive::new(password)
|
||||||
};
|
};
|
||||||
|
|
||||||
super::get("/user/login", ¶ms)
|
super::post("/user/login", ¶ms)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,3 +38,15 @@ where
|
||||||
.send()?
|
.send()?
|
||||||
.json()
|
.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn post<T, Params>(path: &str, params: &Params) -> Result<T, reqwest::Error>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
Params: Serialize + std::fmt::Debug,
|
||||||
|
{
|
||||||
|
CLIENT
|
||||||
|
.post(&get_url(path))
|
||||||
|
.json(¶ms)
|
||||||
|
.send()?
|
||||||
|
.json()
|
||||||
|
}
|
||||||
|
|
176
src/main.rs
176
src/main.rs
|
@ -19,11 +19,15 @@ enum AppState {
|
||||||
Communities,
|
Communities,
|
||||||
Community,
|
Community,
|
||||||
Person,
|
Person,
|
||||||
Post
|
Post,
|
||||||
|
Login,
|
||||||
|
Message
|
||||||
}
|
}
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
state: AppState,
|
state: AppState,
|
||||||
|
message: Option<String>,
|
||||||
|
latest_action: Option<AppMsg>,
|
||||||
posts: FactoryVecDeque<PostRow>,
|
posts: FactoryVecDeque<PostRow>,
|
||||||
communities: FactoryVecDeque<CommunityRow>,
|
communities: FactoryVecDeque<CommunityRow>,
|
||||||
profile_page: Controller<ProfilePage>,
|
profile_page: Controller<ProfilePage>,
|
||||||
|
@ -31,13 +35,18 @@ struct App {
|
||||||
post_page: Controller<PostPage>
|
post_page: Controller<PostPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum AppMsg {
|
pub enum AppMsg {
|
||||||
ChooseInstance,
|
ChooseInstance,
|
||||||
|
ShowLogin,
|
||||||
|
Login(String, String),
|
||||||
|
Logout,
|
||||||
|
Retry,
|
||||||
|
ShowMessage(String),
|
||||||
DoneChoosingInstance(String),
|
DoneChoosingInstance(String),
|
||||||
StartFetchPosts,
|
StartFetchPosts,
|
||||||
DoneFetchPosts(Result<Vec<PostView>, reqwest::Error>),
|
DoneFetchPosts(Vec<PostView>),
|
||||||
DoneFetchCommunities(Result<Vec<CommunityView>, reqwest::Error>),
|
DoneFetchCommunities(Vec<CommunityView>),
|
||||||
ViewCommunities(Option<String>),
|
ViewCommunities(Option<String>),
|
||||||
OpenCommunity(String),
|
OpenCommunity(String),
|
||||||
DoneFetchCommunity(GetCommunityResponse),
|
DoneFetchCommunity(GetCommunityResponse),
|
||||||
|
@ -75,7 +84,6 @@ impl SimpleComponent for App {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
#[name(stack)]
|
|
||||||
match model.state {
|
match model.state {
|
||||||
AppState::Posts => gtk::ScrolledWindow {
|
AppState::Posts => gtk::ScrolledWindow {
|
||||||
set_vexpand: true,
|
set_vexpand: true,
|
||||||
|
@ -118,12 +126,54 @@ impl SimpleComponent for App {
|
||||||
gtk::Button {
|
gtk::Button {
|
||||||
set_label: "Done",
|
set_label: "Done",
|
||||||
connect_clicked[sender, instance_url] => move |_| {
|
connect_clicked[sender, instance_url] => move |_| {
|
||||||
let text = instance_url.buffer().text().as_str().to_string();
|
let text = instance_url.text().as_str().to_string();
|
||||||
instance_url.buffer().set_text("");
|
instance_url.set_text("");
|
||||||
sender.input(AppMsg::DoneChoosingInstance(text));
|
sender.input(AppMsg::DoneChoosingInstance(text));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
AppState::Login => gtk::Box {
|
||||||
|
set_hexpand: true,
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_spacing: 12,
|
||||||
|
set_margin_all: 20,
|
||||||
|
set_valign: gtk::Align::Center,
|
||||||
|
set_hexpand: true,
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
set_text: "Login",
|
||||||
|
add_css_class: "font-bold",
|
||||||
|
},
|
||||||
|
#[name(username)]
|
||||||
|
gtk::Entry {
|
||||||
|
set_placeholder_text: Some("Username or E-Mail"),
|
||||||
|
},
|
||||||
|
#[name(password)]
|
||||||
|
gtk::PasswordEntry {
|
||||||
|
set_placeholder_text: Some("Password"),
|
||||||
|
set_show_peek_icon: true,
|
||||||
|
},
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
set_halign: gtk::Align::End,
|
||||||
|
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Cancel",
|
||||||
|
connect_clicked => AppMsg::StartFetchPosts,
|
||||||
|
set_margin_end: 10,
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Login",
|
||||||
|
connect_clicked[sender, username, password] => move |_| {
|
||||||
|
let username_text = username.text().as_str().to_string();
|
||||||
|
username.set_text("");
|
||||||
|
let password_text = password.text().as_str().to_string();
|
||||||
|
password.set_text("");
|
||||||
|
sender.input(AppMsg::Login(username_text, password_text));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
AppState::Communities => gtk::Box {
|
AppState::Communities => gtk::Box {
|
||||||
gtk::ScrolledWindow {
|
gtk::ScrolledWindow {
|
||||||
set_vexpand: true,
|
set_vexpand: true,
|
||||||
|
@ -145,7 +195,7 @@ impl SimpleComponent for App {
|
||||||
gtk::Button {
|
gtk::Button {
|
||||||
set_label: "Search",
|
set_label: "Search",
|
||||||
connect_clicked[sender, community_search_query] => move |_| {
|
connect_clicked[sender, community_search_query] => move |_| {
|
||||||
let text = community_search_query.buffer().text().as_str().to_string();
|
let text = community_search_query.text().as_str().to_string();
|
||||||
sender.input(AppMsg::ViewCommunities(Some(text)));
|
sender.input(AppMsg::ViewCommunities(Some(text)));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -177,13 +227,30 @@ impl SimpleComponent for App {
|
||||||
post_page -> gtk::ScrolledWindow {}
|
post_page -> gtk::ScrolledWindow {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AppState::Message => {
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_margin_all: 40,
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
#[watch]
|
||||||
|
set_text: &model.message.clone().unwrap_or("".to_string()),
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Go Home",
|
||||||
|
connect_clicked => AppMsg::Retry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu! {
|
menu! {
|
||||||
menu_model: {
|
menu_model: {
|
||||||
"Choose Instance" => ChangeInstanceAction
|
"Choose Instance" => ChangeInstanceAction,
|
||||||
|
"Login" => LoginAction,
|
||||||
|
"Logout" => LogoutAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +270,7 @@ impl SimpleComponent for App {
|
||||||
let community_page = CommunityPage::builder().launch(default_community()).forward(sender.input_sender(), |msg| msg);
|
let community_page = CommunityPage::builder().launch(default_community()).forward(sender.input_sender(), |msg| msg);
|
||||||
let post_page = PostPage::builder().launch(default_post()).forward(sender.input_sender(), |msg| msg);
|
let post_page = PostPage::builder().launch(default_post()).forward(sender.input_sender(), |msg| msg);
|
||||||
|
|
||||||
let model = App { state, posts, communities, profile_page, community_page, post_page };
|
let model = App { state, posts, communities, profile_page, community_page, post_page, message: None, latest_action: None };
|
||||||
|
|
||||||
// fetch posts if that's the initial page
|
// fetch posts if that's the initial page
|
||||||
if !instance_url.is_empty() { sender.input(AppMsg::StartFetchPosts) };
|
if !instance_url.is_empty() { sender.input(AppMsg::StartFetchPosts) };
|
||||||
|
@ -217,13 +284,23 @@ impl SimpleComponent for App {
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
// create the menu and its actions
|
// create the header bar menu and its actions
|
||||||
|
let instance_sender = sender.clone();
|
||||||
let instance_action: RelmAction<ChangeInstanceAction> = RelmAction::new_stateless(move |_| {
|
let instance_action: RelmAction<ChangeInstanceAction> = RelmAction::new_stateless(move |_| {
|
||||||
|
instance_sender.input(AppMsg::ChooseInstance);
|
||||||
|
});
|
||||||
|
let login_sender = sender.clone();
|
||||||
|
let login_action: RelmAction<LoginAction> = RelmAction::new_stateless(move |_| {
|
||||||
|
login_sender.input(AppMsg::ShowLogin);
|
||||||
|
});
|
||||||
|
let logout_action: RelmAction<LogoutAction> = RelmAction::new_stateless(move |_| {
|
||||||
sender.input(AppMsg::ChooseInstance);
|
sender.input(AppMsg::ChooseInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut group = RelmActionGroup::<WindowActionGroup>::new();
|
let mut group = RelmActionGroup::<WindowActionGroup>::new();
|
||||||
group.add_action(instance_action);
|
group.add_action(instance_action);
|
||||||
|
group.add_action(login_action);
|
||||||
|
group.add_action(logout_action);
|
||||||
group.register_for_widget(&widgets.main_window);
|
group.register_for_widget(&widgets.main_window);
|
||||||
|
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
|
@ -244,43 +321,46 @@ impl SimpleComponent for App {
|
||||||
}
|
}
|
||||||
AppMsg::StartFetchPosts => {
|
AppMsg::StartFetchPosts => {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let posts = api::posts::list_posts(1, None);
|
let message = match api::posts::list_posts(1, None) {
|
||||||
sender.input(AppMsg::DoneFetchPosts(posts));
|
Ok(posts) => AppMsg::DoneFetchPosts(posts),
|
||||||
|
Err(err) => AppMsg::ShowMessage(err.to_string())
|
||||||
|
};
|
||||||
|
sender.input(message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AppMsg::DoneFetchPosts(posts) => {
|
AppMsg::DoneFetchPosts(posts) => {
|
||||||
self.state = AppState::Posts;
|
self.state = AppState::Posts;
|
||||||
if let Ok(posts) = posts {
|
|
||||||
self.posts.guard().clear();
|
self.posts.guard().clear();
|
||||||
for post in posts {
|
for post in posts {
|
||||||
self.posts.guard().push_back(post);
|
self.posts.guard().push_back(post);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
AppMsg::ViewCommunities(query) => {
|
AppMsg::ViewCommunities(query) => {
|
||||||
self.state = AppState::Communities;
|
self.state = AppState::Communities;
|
||||||
if (query.is_none() || query.clone().unwrap().trim().is_empty()) && !self.communities.is_empty() { return; }
|
if (query.is_none() || query.clone().unwrap().trim().is_empty()) && !self.communities.is_empty() { return; }
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let communities = api::communities::fetch_communities(1, query);
|
let message = match api::communities::fetch_communities(1, query) {
|
||||||
sender.input(AppMsg::DoneFetchCommunities(communities));
|
Ok(communities) => AppMsg::DoneFetchCommunities(communities),
|
||||||
|
Err(err) => AppMsg::ShowMessage(err.to_string())
|
||||||
|
};
|
||||||
|
sender.input(message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AppMsg::DoneFetchCommunities(communities) => {
|
AppMsg::DoneFetchCommunities(communities) => {
|
||||||
self.state = AppState::Communities;
|
self.state = AppState::Communities;
|
||||||
if let Ok(communities) = communities {
|
|
||||||
self.communities.guard().clear();
|
self.communities.guard().clear();
|
||||||
for community in communities {
|
for community in communities {
|
||||||
self.communities.guard().push_back(community);
|
self.communities.guard().push_back(community);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
AppMsg::OpenPerson(person_name) => {
|
AppMsg::OpenPerson(person_name) => {
|
||||||
self.state = AppState::Loading;
|
self.state = AppState::Loading;
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let person = api::user::get_user(person_name, 1);
|
let message = match api::user::get_user(person_name, 1) {
|
||||||
if let Ok(person) = person {
|
Ok(person) => AppMsg::DoneFetchPerson(person),
|
||||||
sender.input(AppMsg::DoneFetchPerson(person));
|
Err(err) => AppMsg::ShowMessage(err.to_string())
|
||||||
}
|
};
|
||||||
|
sender.input(message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AppMsg::DoneFetchPerson(person) => {
|
AppMsg::DoneFetchPerson(person) => {
|
||||||
|
@ -290,10 +370,11 @@ impl SimpleComponent for App {
|
||||||
AppMsg::OpenCommunity(community_name) => {
|
AppMsg::OpenCommunity(community_name) => {
|
||||||
self.state = AppState::Loading;
|
self.state = AppState::Loading;
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let community = api::community::get_community(community_name);
|
let message = match api::community::get_community(community_name) {
|
||||||
if let Ok(community) = community {
|
Ok(community) => AppMsg::DoneFetchCommunity(community),
|
||||||
sender.input(AppMsg::DoneFetchCommunity(community));
|
Err(err) => AppMsg::ShowMessage(err.to_string())
|
||||||
}
|
};
|
||||||
|
sender.input(message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AppMsg::DoneFetchCommunity(community) => {
|
AppMsg::DoneFetchCommunity(community) => {
|
||||||
|
@ -303,22 +384,55 @@ impl SimpleComponent for App {
|
||||||
AppMsg::OpenPost(post_id) => {
|
AppMsg::OpenPost(post_id) => {
|
||||||
self.state = AppState::Loading;
|
self.state = AppState::Loading;
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let post = api::post::get_post(post_id);
|
let message = match api::post::get_post(post_id) {
|
||||||
if let Ok(post) = post {
|
Ok(post) => AppMsg::DoneFetchPost(post),
|
||||||
sender.input(AppMsg::DoneFetchPost(post));
|
Err(err) => AppMsg::ShowMessage(err.to_string())
|
||||||
}
|
};
|
||||||
|
sender.input(message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AppMsg::DoneFetchPost(post) => {
|
AppMsg::DoneFetchPost(post) => {
|
||||||
self.post_page.sender().emit(post_page::PostInput::UpdatePost(post));
|
self.post_page.sender().emit(post_page::PostInput::UpdatePost(post));
|
||||||
self.state = AppState::Post;
|
self.state = AppState::Post;
|
||||||
}
|
}
|
||||||
|
AppMsg::ShowLogin => {
|
||||||
|
self.state = AppState::Login;
|
||||||
|
}
|
||||||
|
AppMsg::Login(username, password) => {
|
||||||
|
self.state = AppState::Loading;
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let message = match api::auth::login(username, password) {
|
||||||
|
Ok(login) => {
|
||||||
|
if let Some(token) = login.jwt {
|
||||||
|
util::set_auth_token(Some(token));
|
||||||
|
AppMsg::StartFetchPosts
|
||||||
|
} else {
|
||||||
|
AppMsg::ShowMessage("Wrong credentials!".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => AppMsg::ShowMessage(err.to_string())
|
||||||
|
};
|
||||||
|
sender.input(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
AppMsg::Logout => {
|
||||||
|
util::set_auth_token(None);
|
||||||
|
}
|
||||||
|
AppMsg::ShowMessage(message) => {
|
||||||
|
self.message = Some(message);
|
||||||
|
self.state = AppState::Message;
|
||||||
|
}
|
||||||
|
AppMsg::Retry => {
|
||||||
|
sender.input(self.latest_action.clone().unwrap_or(AppMsg::StartFetchPosts));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
relm4::new_action_group!(WindowActionGroup, "win");
|
relm4::new_action_group!(WindowActionGroup, "win");
|
||||||
relm4::new_stateless_action!(ChangeInstanceAction, WindowActionGroup, "instance");
|
relm4::new_stateless_action!(ChangeInstanceAction, WindowActionGroup, "instance");
|
||||||
|
relm4::new_stateless_action!(LoginAction, WindowActionGroup, "login");
|
||||||
|
relm4::new_stateless_action!(LogoutAction, WindowActionGroup, "logout");
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let app = RelmApp::new(APP_ID);
|
let app = RelmApp::new(APP_ID);
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use std::{fs::File, path::PathBuf};
|
use std::{fs::File, path::PathBuf};
|
||||||
use crate::gtk::glib;
|
use crate::gtk::glib;
|
||||||
|
use lemmy_api_common::sensitive::Sensitive;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::APP_ID;
|
use crate::APP_ID;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default)]
|
#[derive(Deserialize, Serialize, Default)]
|
||||||
pub struct Preferences {
|
pub struct Preferences {
|
||||||
pub instance_url: String,
|
pub instance_url: String,
|
||||||
|
pub jwt: Option<Sensitive<String>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_path() -> PathBuf {
|
pub fn data_path() -> PathBuf {
|
||||||
|
|
14
src/util.rs
14
src/util.rs
|
@ -1,6 +1,8 @@
|
||||||
use lemmy_api_common::lemmy_db_schema::newtypes::DbUrl;
|
use lemmy_api_common::{lemmy_db_schema::newtypes::DbUrl, sensitive::Sensitive};
|
||||||
use relm4_components::web_image::WebImageMsg;
|
use relm4_components::web_image::WebImageMsg;
|
||||||
|
|
||||||
|
use crate::settings;
|
||||||
|
|
||||||
pub fn get_web_image_msg(url: Option<DbUrl>) -> WebImageMsg {
|
pub fn get_web_image_msg(url: Option<DbUrl>) -> WebImageMsg {
|
||||||
return if let Some(url) = url {
|
return if let Some(url) = url {
|
||||||
WebImageMsg::LoadImage(url.to_string())
|
WebImageMsg::LoadImage(url.to_string())
|
||||||
|
@ -16,3 +18,13 @@ pub fn get_web_image_url(url: Option<DbUrl>) -> String {
|
||||||
pub fn markdown_to_pango_markup(text: String) -> String {
|
pub fn markdown_to_pango_markup(text: String) -> String {
|
||||||
return html2pango::markup_html(&markdown::to_html(&text)).unwrap_or(text)
|
return html2pango::markup_html(&markdown::to_html(&text)).unwrap_or(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_auth_token(token: Option<Sensitive<String>>) {
|
||||||
|
let mut settings = settings::get_prefs();
|
||||||
|
settings.jwt = token.clone();
|
||||||
|
settings::save_prefs(&settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_auth_token() -> Option<Sensitive<String>> {
|
||||||
|
settings::get_prefs().jwt
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue