Add HTTP client and login support

This commit is contained in:
Elias Projahn 2020-11-14 22:32:21 +01:00
parent d20d80d1ac
commit ea3bd35ffd
16 changed files with 832 additions and 25 deletions

View file

@ -0,0 +1,88 @@
use crate::backend::{Backend, LoginData};
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for entering login credentials.
pub struct LoginDialog {
backend: Rc<Backend>,
window: libhandy::Window,
stack: gtk::Stack,
info_bar: gtk::InfoBar,
username_entry: gtk::Entry,
password_entry: gtk::Entry,
selected_cb: RefCell<Option<Box<dyn Fn(LoginData) -> ()>>>,
}
impl LoginDialog {
/// Create a new login dialog.
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/login_dialog.ui");
get_widget!(builder, libhandy::Window, window);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::InfoBar, info_bar);
get_widget!(builder, gtk::Button, cancel_button);
get_widget!(builder, gtk::Button, login_button);
get_widget!(builder, gtk::Entry, username_entry);
get_widget!(builder, gtk::Entry, password_entry);
window.set_transient_for(Some(parent));
let this = Rc::new(Self {
backend,
window,
stack,
info_bar,
username_entry,
password_entry,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close();
}));
login_button.connect_clicked(clone!(@strong this => move |_| {
this.stack.set_visible_child_name("loading");
let data = LoginData {
username: this.username_entry.get_text().to_string(),
password: this.password_entry.get_text().to_string(),
};
let c = glib::MainContext::default();
let clone = this.clone();
c.spawn_local(async move {
clone.backend.set_login_data(data.clone()).await.unwrap();
if clone.backend.login().await.unwrap() {
if let Some(cb) = &*clone.selected_cb.borrow() {
cb(data);
}
clone.window.close();
} else {
clone.stack.set_visible_child_name("content");
clone.info_bar.set_revealed(true);
}
});
}));
this
}
/// The closure to call when the login succeded.
pub fn set_selected_cb<F: Fn(LoginData) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the login dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -13,6 +13,9 @@ pub use instrument_editor::*;
pub mod instrument_selector;
pub use instrument_selector::*;
pub mod login_dialog;
pub use login_dialog::*;
pub mod person_editor;
pub use person_editor::*;
@ -22,6 +25,9 @@ pub use person_selector::*;
pub mod preferences;
pub use preferences::*;
pub mod server_dialog;
pub use server_dialog::*;
pub mod recording;
pub use recording::*;

View file

@ -1,3 +1,4 @@
use super::{LoginDialog, ServerDialog};
use crate::backend::Backend;
use gettextrs::gettext;
use glib::clone;
@ -6,47 +7,98 @@ use gtk_macros::get_widget;
use libhandy::prelude::*;
use std::rc::Rc;
/// A dialog for configuring the app.
pub struct Preferences {
backend: Rc<Backend>,
window: libhandy::Window,
music_library_path_row: libhandy::ActionRow,
url_row: libhandy::ActionRow,
login_row: libhandy::ActionRow,
}
impl Preferences {
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Self {
/// Create a new preferences dialog.
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/preferences.ui");
get_widget!(builder, libhandy::Window, window);
get_widget!(builder, libhandy::ActionRow, music_library_path_row);
get_widget!(builder, gtk::Button, select_music_library_path_button);
get_widget!(builder, libhandy::ActionRow, url_row);
get_widget!(builder, gtk::Button, url_button);
get_widget!(builder, libhandy::ActionRow, login_row);
get_widget!(builder, gtk::Button, login_button);
window.set_transient_for(Some(parent));
if let Some(path) = backend.get_music_library_path() {
music_library_path_row.set_subtitle(Some(path.to_str().unwrap()));
let this = Rc::new(Self {
backend,
window,
music_library_path_row,
url_row,
login_row,
});
// Connect signals and callbacks
select_music_library_path_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = gtk::FileChooserNative::new(
Some(&gettext("Select music library folder")),
Some(&this.window), gtk::FileChooserAction::SelectFolder,None, None);
if let gtk::ResponseType::Accept = dialog.run() {
if let Some(path) = dialog.get_filename() {
this.music_library_path_row.set_subtitle(Some(path.to_str().unwrap()));
let context = glib::MainContext::default();
let backend = this.backend.clone();
context.spawn_local(async move {
backend.set_music_library_path(path).await.unwrap();
});
}
}
}));
url_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = ServerDialog::new(this.backend.clone(), &this.window);
dialog.set_selected_cb(clone!(@strong this => move |url| {
this.url_row.set_subtitle(Some(&url));
}));
dialog.show();
}));
login_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = LoginDialog::new(this.backend.clone(), &this.window);
dialog.set_selected_cb(clone!(@strong this => move |data| {
this.login_row.set_subtitle(Some(&data.username));
}));
dialog.show();
}));
// Initialize
if let Some(path) = this.backend.get_music_library_path() {
this.music_library_path_row
.set_subtitle(Some(path.to_str().unwrap()));
}
select_music_library_path_button.connect_clicked(
clone!(@strong window, @strong backend, @strong music_library_path_row => move |_| {
let dialog = gtk::FileChooserNative::new(
Some(&gettext("Select music library folder")),
Some(&window), gtk::FileChooserAction::SelectFolder,None, None);
if let Some(url) = this.backend.get_server_url() {
this.url_row.set_subtitle(Some(&url));
}
if let gtk::ResponseType::Accept = dialog.run() {
if let Some(path) = dialog.get_filename() {
music_library_path_row.set_subtitle(Some(path.to_str().unwrap()));
if let Some(data) = this.backend.get_login_data() {
this.login_row.set_subtitle(Some(&data.username));
}
let context = glib::MainContext::default();
let backend = backend.clone();
context.spawn_local(async move {
backend.set_music_library_path(path).await.unwrap();
});
}
}
}),
);
Self { window }
this
}
/// Show the preferences dialog.
pub fn show(&self) {
self.window.show();
}

View file

@ -0,0 +1,65 @@
use crate::backend::Backend;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for setting up the server.
pub struct ServerDialog {
backend: Rc<Backend>,
window: libhandy::Window,
url_entry: gtk::Entry,
selected_cb: RefCell<Option<Box<dyn Fn(String) -> ()>>>,
}
impl ServerDialog {
/// Create a new server dialog.
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/server_dialog.ui");
get_widget!(builder, libhandy::Window, window);
get_widget!(builder, gtk::Button, cancel_button);
get_widget!(builder, gtk::Button, set_button);
get_widget!(builder, gtk::Entry, url_entry);
window.set_transient_for(Some(parent));
let this = Rc::new(Self {
backend,
window,
url_entry,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close();
}));
set_button.connect_clicked(clone!(@strong this => move |_| {
let url = this.url_entry.get_text().to_string();
this.backend.set_server_url(&url).unwrap();
if let Some(cb) = &*this.selected_cb.borrow() {
cb(url);
}
this.window.close();
}));
this
}
/// The closure to call when the server was set.
pub fn set_selected_cb<F: Fn(String) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the server dialog.
pub fn show(&self) {
self.window.show();
}
}