diff options
author | Vika <vika@fireburn.ru> | 2024-08-24 02:26:40 +0300 |
---|---|---|
committer | Vika <vika@fireburn.ru> | 2024-08-24 02:35:03 +0300 |
commit | a4ad4fdb8d56813871322bbc9673323218635b93 (patch) | |
tree | 037fcd6025fac4e963fb931017429ac04d5e3abf /src/lib.rs | |
parent | c8f4b5240b8bcfb5b575bd12b09c68e96e15d37f (diff) | |
download | bowl-a4ad4fdb8d56813871322bbc9673323218635b93.tar.zst |
Very crude mock-up for an authentication screen
This saves memory by dropping unneeded components. Once the app changes state, it can simply drop the unnecessary component, such as the login screen, to save memory.
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 208 |
1 files changed, 146 insertions, 62 deletions
diff --git a/src/lib.rs b/src/lib.rs index 302c63a..f2436f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{borrow::Borrow, sync::Arc}; use adw::prelude::*; use relm4::{gtk, prelude::{AsyncComponent, AsyncComponentParts, ComponentController, Controller}, AsyncComponentSender, Component, RelmWidgetExt}; @@ -29,84 +29,134 @@ pub const VISIBILITY: [&str; 2] = ["public", "private"]; #[derive(Debug)] pub struct App { - micropub: Arc<micropub::Client>, - submit_busy_guard: Option<gtk::gio::ApplicationBusyGuard>, - - post_editor: Controller<components::PostEditor<micropub::Error>> + state: AuthState +} +#[derive(Debug)] +enum AuthState { + LoggedOut(gtk::EntryBuffer), + LoggedIn { + submit_busy_guard: Option<gtk::gio::ApplicationBusyGuard>, + post_editor: Controller<components::PostEditor<micropub::Error>>, + micropub: micropub::Client + } } #[derive(Debug)] #[doc(hidden)] pub enum Input { SubmitButtonPressed, + Authorize, PostEditor(Option<Post>) } -#[relm4::component(pub async)] +#[derive(Default, Debug)] +pub struct AppRootWidgets { + root: adw::ApplicationWindow, + toolbar_view: adw::ToolbarView, + top_bar: adw::HeaderBar, + top_bar_btn: gtk::Button, + + login_box: gtk::Box, + login_label: gtk::Label, + login_button: gtk::Button, +} + +//#[relm4::component(pub async)] impl AsyncComponent for App { /// The type of the messages that this component can receive. type Input = Input; /// The type of the messages that this component can send. type Output = (); /// The type of data with which this component will be initialized. - type Init = micropub::Client; + type Init = (); /// The type of the command outputs that this component can receive. type CommandOutput = (); - - view! { - #[root] - adw::ApplicationWindow { - set_title: Some("Create post"), - set_width_request: 360, - set_height_request: 294, - - adw::ToolbarView { - add_top_bar: &{ - relm4::view! { - send_button = gtk::Button { - set_icon_name: "document-send-symbolic", - set_tooltip: "Send post", - - connect_clicked => Self::Input::SubmitButtonPressed, - #[watch] - set_sensitive: model.submit_busy_guard.is_none(), - }, - bar = adw::HeaderBar::new() { - pack_end: &send_button, - }, - } + type Widgets = AppRootWidgets; - bar - }, + type Root = adw::ApplicationWindow; - model.post_editor.widget(), - } - } - + fn init_root() -> Self::Root { + let window = Self::Root::default(); + window.set_size_request(360, 294); + #[cfg(debug_assertions)] + window.add_css_class("devel"); + + window } + /// Initialize the UI and model. async fn init( - init: Self::Init, + _init: Self::Init, window: Self::Root, sender: AsyncComponentSender<Self>, ) -> AsyncComponentParts<Self> { let model = App { - submit_busy_guard: None, - micropub: Arc::new(init), - post_editor: components::PostEditor::builder() - .launch(None) - .forward(sender.input_sender(), Self::Input::PostEditor), + state: AuthState::LoggedOut(Default::default()) }; - let widgets = view_output!(); + let mut widgets = Self::Widgets { + root: window, + ..Self::Widgets::default() + }; - #[cfg(debug_assertions)] - window.add_css_class("devel"); + widgets.toolbar_view.add_top_bar(&widgets.top_bar); + + widgets.top_bar.pack_end(&widgets.top_bar_btn); + + widgets.top_bar_btn.set_icon_name("document-send-symbolic"); + widgets.top_bar_btn.set_tooltip("Send post"); + widgets.top_bar_btn.connect_clicked(glib::clone!( + #[strong] sender, + move |_button| sender.input(Self::Input::SubmitButtonPressed) + )); + + widgets.login_box.set_orientation(gtk::Orientation::Vertical); + widgets.login_box.append(&widgets.login_label); + widgets.login_box.append(&widgets.login_button); + + widgets.login_label.set_text("You need to authorize first."); + + widgets.login_button.set_label("Pretend to authorize"); + widgets.login_button.set_tooltip("(check MICROPUB_URI and MICROPUB_TOKEN environment variables)"); + widgets.login_button.connect_clicked(glib::clone!( + #[strong] sender, + move |_button| sender.input(Self::Input::Authorize) + )); + + widgets.root.set_content(Some(&widgets.toolbar_view)); + + // Separate component choosing logic from initialization. We + // already have all the parts here, might as well use them. + model.update_view(&mut widgets, sender); AsyncComponentParts { model, widgets } } + + fn update_view(&self, widgets: &mut Self::Widgets, _sender: AsyncComponentSender<Self>) { + // Bind the child component, if any, here. + match &self.state { + AuthState::LoggedOut(_entry_buffer) => { + widgets.root.set_title(Some("Sign in with your website")); + widgets.toolbar_view.set_content(None::<>k::Box>); + widgets.top_bar_btn.set_visible(false); + widgets.toolbar_view.set_content(Some(&widgets.login_box)); + }, + AuthState::LoggedIn { + post_editor, + submit_busy_guard, + .. + } => { + widgets.root.set_title(Some("Create post")); + widgets.toolbar_view.set_content(Some(post_editor.widget())); + widgets.top_bar_btn.set_sensitive(submit_busy_guard.is_none()); + widgets.top_bar_btn.set_visible(true); + } + } + } + + async fn update( &mut self, message: Self::Input, @@ -114,30 +164,64 @@ impl AsyncComponent for App { _root: &Self::Root ) { match message { + Input::Authorize => { + self.state = AuthState::LoggedIn { + post_editor: components::PostEditor::builder() + .launch(None) + .forward(_sender.clone().input_sender(), Self::Input::PostEditor), + micropub: std::env::var("MICROPUB_TOKEN").ok() + .and_then(|token| { + Some((token, glib::Uri::parse( + &std::env::var("MICROPUB_URI").ok()?, + glib::UriFlags::NONE + ).ok()?)) + }) + .map(|(token, uri)| crate::micropub::Client::new( + uri, token + )) + .unwrap(), + submit_busy_guard: None + }; + }, Input::SubmitButtonPressed => { - self.submit_busy_guard = Some(relm4::main_adw_application().mark_busy()); - self.post_editor.sender().send(components::PostEditorInput::Submit).unwrap(); + if let AuthState::LoggedIn { + ref mut submit_busy_guard, + ref post_editor, + .. + } = &mut self.state { + *submit_busy_guard = Some(relm4::main_adw_application().mark_busy()); + post_editor.sender().emit(components::PostEditorInput::Submit); + }; }, Input::PostEditor(None) => { - self.submit_busy_guard = None; + if let AuthState::LoggedIn { + ref mut submit_busy_guard, + .. + } = &mut self.state { + *submit_busy_guard = None; + } } Input::PostEditor(Some(post)) => { - let mf2 = post.into(); - log::debug!("Submitting post: {:#}", serde_json::to_string(&mf2).unwrap()); - match self.micropub.send_post(mf2).await { - Ok(location) => { - self.post_editor.sender() - .send(components::PostEditorInput::SubmitDone(location)) - .unwrap(); - }, - Err(err) => { - log::warn!("Error sending post: {}", err); - self.post_editor.sender() - .send(components::PostEditorInput::SubmitError(err)) - .unwrap(); + if let AuthState::LoggedIn { + ref mut submit_busy_guard, + ref post_editor, + ref micropub, + } = &mut self.state { + let mf2 = post.into(); + log::debug!("Submitting post: {:#}", serde_json::to_string(&mf2).unwrap()); + match micropub.send_post(mf2).await { + Ok(uri) => { + post_editor.sender() + .emit(components::PostEditorInput::SubmitDone(uri)); + }, + Err(err) => { + log::warn!("Error sending post: {}", err); + post_editor.sender() + .emit(components::PostEditorInput::SubmitError(err)); + } } + *submit_busy_guard = None; } - self.submit_busy_guard = None; }, } } |