diff options
-rw-r--r-- | src/lib.rs | 208 | ||||
-rw-r--r-- | 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<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; }, } } 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::<App>( - 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::<bowl::App>(()); } |