From a4ad4fdb8d56813871322bbc9673323218635b93 Mon Sep 17 00:00:00 2001 From: Vika Date: Sat, 24 Aug 2024 02:26:40 +0300 Subject: 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. --- src/lib.rs | 208 ++++++++++++++++++++++++++++++++++++++++++------------------ src/main.rs | 17 +---- 2 files changed, 149 insertions(+), 76 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, - submit_busy_guard: Option, - - post_editor: Controller> + state: AuthState +} +#[derive(Debug)] +enum AuthState { + LoggedOut(gtk::EntryBuffer), + LoggedIn { + submit_busy_guard: Option, + post_editor: Controller>, + micropub: micropub::Client + } } #[derive(Debug)] #[doc(hidden)] pub enum Input { SubmitButtonPressed, + Authorize, PostEditor(Option) } -#[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, ) -> AsyncComponentParts { 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) { + // 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; }, } } diff --git a/src/main.rs b/src/main.rs index 31d7d24..a12fe60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,3 @@ -use adw::prelude::GtkWindowExt; -use relm4::{ComponentParts, ComponentSender, RelmApp, Component, ComponentController}; - -use bowl::App; - -use bowl::APPLICATION_ID; - static GLIB_LOGGER: glib::GlibLogger = glib::GlibLogger::new( glib::GlibLoggerFormat::Plain, glib::GlibLoggerDomain::CrateTarget, @@ -14,11 +7,7 @@ fn main() { log::set_logger(&GLIB_LOGGER).unwrap(); log::set_max_level(log::LevelFilter::Debug); - let app = RelmApp::new(APPLICATION_ID); - app.run_async::( - bowl::micropub::Client::new( - glib::Uri::parse(&std::env::var("MICROPUB_URI").unwrap(), glib::UriFlags::NONE).unwrap(), - std::env::var("MICROPUB_TOKEN").unwrap(), - ) - ); + let app = relm4::RelmApp::new(bowl::APPLICATION_ID); + + app.run_async::(()); } -- cgit 1.4.1