diff options
Diffstat (limited to 'gui/src/app/mod.rs')
-rw-r--r-- | gui/src/app/mod.rs | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs new file mode 100644 index 0000000..f66bd7c --- /dev/null +++ b/gui/src/app/mod.rs @@ -0,0 +1,259 @@ +use std::sync::Arc; + +use anyhow::Result; +use iced::Application; +use iced::Column; +use iced::Container; +use iced::Length; +use iced::Row; +use iced::Scrollable; +use iced::TextInput; +use iced::scrollable; +use iced::text_input; +use distrox_lib::profile::Profile; +use tokio::sync::RwLock; + +use crate::timeline::Timeline; +use crate::timeline::PostLoadingRecipe; + +mod message; +pub use message::Message; + +mod handler; + + +use crate::gossip::GossipRecipe; + +#[derive(Debug)] +pub(crate) enum Distrox { + Loading { + gossip_subscription_recv: RwLock<tokio::sync::oneshot::Receiver<GossipRecipe>>, + }, + Loaded { + profile: Arc<RwLock<Profile>>, + gossip_subscription_recv: RwLock<tokio::sync::oneshot::Receiver<GossipRecipe>>, + + scroll: scrollable::State, + input: text_input::State, + input_value: String, + timeline: Timeline, + + log_visible: bool, + log: std::collections::VecDeque<String>, + }, + FailedToStart, +} + +impl Application for Distrox { + type Executor = iced::executor::Default; // tokio + type Message = Message; + type Flags = String; + + fn new(name: String) -> (Self, iced::Command<Self::Message>) { + let (gossip_subscription_sender, gossip_subscription_recv) = tokio::sync::oneshot::channel(); + ( + Distrox::Loading { + gossip_subscription_recv: RwLock::new(gossip_subscription_recv), + }, + + iced::Command::perform(async move { + let profile = match Profile::load(&name).await { + Err(e) => return Message::FailedToLoad(e.to_string()), + Ok(instance) => Arc::new(RwLock::new(instance)), + }; + + if let Err(e) = profile + .read() + .await + .client() + .pubsub_subscribe("distrox".to_string()) + .await + .map_err(anyhow::Error::from) + .map(|stream| { + log::trace!("Subscription to 'distrox' pubsub channel worked"); + GossipRecipe::new(profile.clone(), stream) + }) + .and_then(|s| gossip_subscription_sender.send(s).map_err(|_| anyhow::anyhow!("Failed to initialize gossipping module"))) + { + log::error!("Failed to load gossip recipe"); + return Message::FailedToLoad(e.to_string()) + } + + Message::Loaded(profile) + }, |m: Message| -> Message { m }) + ) + } + + fn title(&self) -> String { + String::from("distrox") + } + + fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> { + handler::handle_message(self, message) + } + + fn view(&mut self) -> iced::Element<Self::Message> { + match self { + Distrox::Loading { .. } => { + let text = iced::Text::new("Loading"); + + let content = Column::new() + .spacing(20) + .push(text); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } + + Distrox::Loaded { input, input_value, timeline, scroll, log_visible, log, .. } => { + let left_column = Column::new() + .into(); + + let mid_column = Column::new() + .push({ + let input = TextInput::new( + input, + "What do you want to tell the world?", + input_value, + Message::InputChanged, + ) + .padding(15) + .size(12) + .on_submit(Message::CreatePost); + + let timeline = timeline.view(); + + Scrollable::new(scroll) + .padding(40) + .push(input) + .push(timeline) + }) + .into(); + + let right_column = Column::new() + .into(); + + let content = Row::with_children(vec![ + left_column, + mid_column, + right_column + ]) + .spacing(20) + .height(Length::Fill) + .width(Length::Fill); + + let content = Column::new() + .height(Length::Fill) + .width(Length::Fill) + .push(content); + + if *log_visible { + let log = Column::with_children({ + log.iter() + .map(iced::Text::new) + .map(|txt| txt.size(8)) + .map(iced::Element::from) + .collect() + }); + content.push(log) + } else { + content + }.into() + } + + Distrox::FailedToStart => { + unimplemented!() + } + } + } + + fn subscription(&self) -> iced::Subscription<Self::Message> { + let post_loading_subs = match self { + Distrox::Loaded { profile, .. } => { + let profile = match profile.try_read() { + Err(_) => return iced::Subscription::none(), + Ok(p) => p, + }; + + match profile.head() { + None => iced::Subscription::none(), + Some(head) => { + iced::Subscription::from_recipe({ + PostLoadingRecipe::new(profile.client().clone(), head.clone()) + }) + } + } + } + _ => iced::Subscription::none(), + }; + + let keyboard_subs = { + use iced_native::event::Event; + + iced_native::subscription::events_with(|event, _| { + match event { + Event::Keyboard(iced_native::keyboard::Event::KeyPressed { key_code, .. }) => { + if key_code == iced_native::keyboard::KeyCode::F11 { + Some(Message::ToggleLog) + } else { + None + } + }, + _ => None, + } + }) + }; + + let gossip_sub = match self { + Distrox::Loaded { gossip_subscription_recv, .. } => { + match gossip_subscription_recv.try_write() { + Err(_) => None, + Ok(mut sub) => sub.try_recv() + .ok() + .map(|sub| iced::Subscription::from_recipe(sub)), + } + }, + _ => None, + }; + + let gossip_sending_sub = { + iced::time::every(std::time::Duration::from_secs(5)) + .map(|_| Message::PublishGossipAboutMe) + }; + + let mut subscriptions = vec![ + post_loading_subs, + keyboard_subs, + gossip_sending_sub, + ]; + + if let Some(gossip_sub) = gossip_sub { + subscriptions.push(gossip_sub); + } + + iced::Subscription::batch(subscriptions) + } + +} + +pub fn run(name: String) -> Result<()> { + let settings = iced::Settings { + window: iced::window::Settings { + resizable: true, + decorations: true, + transparent: false, + always_on_top: false, + ..iced::window::Settings::default() + }, + flags: name, + exit_on_close_request: true, + ..iced::Settings::default() + }; + + Distrox::run(settings).map_err(anyhow::Error::from) +} + |