diff --git a/src/components/community_page.rs b/src/components/community_page.rs index e197b58..0f03ca7 100644 --- a/src/components/community_page.rs +++ b/src/components/community_page.rs @@ -54,6 +54,11 @@ impl SimpleComponent for CommunityPage { view! { gtk::ScrolledWindow { set_vexpand: false, + connect_edge_reached[sender] => move |_, pos| { + if pos == gtk::PositionType::Bottom && settings::get_prefs().infinite_scroll { + sender.input(CommunityInput::FetchPosts); + } + }, gtk::Box { set_orientation: gtk::Orientation::Vertical, diff --git a/src/components/posts_page.rs b/src/components/posts_page.rs index 8b4e582..7bacc23 100644 --- a/src/components/posts_page.rs +++ b/src/components/posts_page.rs @@ -7,6 +7,8 @@ use relm4::{factory::FactoryVecDeque, prelude::*}; use crate::api; +use crate::settings::get_prefs; + use super::{ post_row::PostRow, sort_dropdown::{SortDropdown, SortDropdownOutput}, @@ -36,6 +38,13 @@ impl SimpleComponent for PostsPage { view! { gtk::ScrolledWindow { set_hexpand: true, + connect_edge_reached[sender] => move |_, pos| { + if pos == gtk::PositionType::Bottom && get_prefs().infinite_scroll { + sender.input( + PostsPageInput::FetchPosts(model.posts_type, model.posts_order, false) + ); + } + }, gtk::Box { set_orientation: gtk::Orientation::Vertical, diff --git a/src/components/profile_page.rs b/src/components/profile_page.rs index b31d0cb..9513356 100644 --- a/src/components/profile_page.rs +++ b/src/components/profile_page.rs @@ -9,9 +9,9 @@ use crate::dialogs::editor::EditorDialog; use crate::dialogs::editor::EditorOutput; use crate::dialogs::editor::EditorType; use crate::settings; +use crate::util::format_elapsed_time; use crate::util::get_web_image_msg; use crate::util::markdown_to_pango_markup; -use crate::util::format_elapsed_time; use super::comment_row::CommentRow; use super::moderates_row::ModeratesRow; diff --git a/src/dialogs/mod.rs b/src/dialogs/mod.rs index c6e3313..6cf6743 100644 --- a/src/dialogs/mod.rs +++ b/src/dialogs/mod.rs @@ -1,3 +1,4 @@ pub mod about; pub mod editor; +pub mod settings; pub mod site_info; diff --git a/src/dialogs/settings.rs b/src/dialogs/settings.rs new file mode 100644 index 0000000..280a76c --- /dev/null +++ b/src/dialogs/settings.rs @@ -0,0 +1,81 @@ +use crate::settings::{get_prefs, save_prefs}; +use gtk::prelude::*; +use relm4::prelude::*; + +pub struct Settings { + visible: bool, +} + +#[derive(Debug)] +pub enum SettingsInput { + Show, + Hide, +} + +#[relm4::component(pub)] +impl SimpleComponent for Settings { + type Init = (); + type Input = SettingsInput; + type Output = crate::AppMsg; + + view! { + dialog = gtk::Dialog { + #[watch] + set_visible: model.visible, + set_modal: true, + set_title: Some("Settings"), + connect_close_request[sender] => move |_| { + sender.input(SettingsInput::Hide); + gtk::Inhibit(false) + }, + + #[wrap(Some)] + set_titlebar = >k::HeaderBar{ + set_show_title_buttons: true, + }, + + gtk::ScrolledWindow { + set_size_request: (400, 400), + + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + set_margin_all: 15, + + gtk::CheckButton { + set_active: get_prefs().infinite_scroll, + set_margin_all: 12, + set_label: Some("Infinite scroll"), + set_tooltip:"Fetch new content automatically when scrolling down", + connect_toggled => move |checkbox| { + let mut prefs = get_prefs(); + prefs.infinite_scroll = checkbox.is_active(); + save_prefs(&prefs); + }, + }, + } + } + } + } + + fn init( + _init: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + let model = Self { visible: false }; + let widgets = view_output!(); + ComponentParts { model, widgets } + } + + fn update(&mut self, message: Self::Input, _sender: ComponentSender) { + match message { + SettingsInput::Show => { + self.visible = true; + } + SettingsInput::Hide => { + self.visible = false; + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 6fd72cb..ebb22d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ use components::{ }; use dialogs::{ about::AboutDialog, + settings::Settings, site_info::{SiteInfo, SiteInfoInput}, }; use gtk::prelude::*; @@ -68,6 +69,7 @@ struct App { login_page: Controller, accounts_page: Controller, saved_page: Controller, + settings_dialog: Controller, about_dialog: Controller, site_info: Controller, logged_in: bool, @@ -222,6 +224,7 @@ impl SimpleComponent for App { "Login" => LoginAction, "Profile" => ProfileAction, "Site Info" => SiteInfoAction, + "Settings" => SettingsAction, "About" => AboutAction } } @@ -262,6 +265,7 @@ impl SimpleComponent for App { let communities_page = CommunitiesPage::builder() .launch(()) .forward(sender.input_sender(), |msg| msg); + let settings_dialog = dialogs::settings::Settings::builder().launch(()).detach(); let about_dialog = AboutDialog::builder() .launch(root.toplevel_window().unwrap()) .detach(); @@ -293,6 +297,7 @@ impl SimpleComponent for App { accounts_page, message: None, about_dialog, + settings_dialog, saved_page, site_info, logged_in, @@ -340,7 +345,6 @@ impl SimpleComponent for App { }) }; let login_action: RelmAction = { - let sender = sender.clone(); RelmAction::new_stateless(move |_| { sender.input(AppMsg::UpdateState(AppState::Login)); }) @@ -351,6 +355,14 @@ impl SimpleComponent for App { sender.emit(SiteInfoInput::Fetch); }) }; + let settings_action = { + let sender = model.settings_dialog.sender().clone(); + RelmAction::::new_stateless(move |_| { + sender + .send(dialogs::settings::SettingsInput::Show) + .unwrap_or_default(); + }) + }; let about_action = { let sender = model.about_dialog.sender().clone(); RelmAction::::new_stateless(move |_| { @@ -364,6 +376,7 @@ impl SimpleComponent for App { group.add_action(profile_action); group.add_action(login_action); group.add_action(site_info_action); + group.add_action(settings_action); group.add_action(about_action); group.register_for_widget(&widgets.main_window); @@ -493,6 +506,7 @@ relm4::new_stateless_action!(AccountsAction, WindowActionGroup, "accounts"); relm4::new_stateless_action!(LoginAction, WindowActionGroup, "login"); relm4::new_stateless_action!(ProfileAction, WindowActionGroup, "profile"); relm4::new_stateless_action!(SiteInfoAction, WindowActionGroup, "site_info"); +relm4::new_stateless_action!(SettingsAction, WindowActionGroup, "settings"); relm4::new_stateless_action!(AboutAction, WindowActionGroup, "about"); fn main() { diff --git a/src/settings.rs b/src/settings.rs index ed28c52..eb920a4 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -12,10 +12,21 @@ pub struct Account { pub name: String, } -#[derive(Deserialize, Serialize, Default)] +#[derive(Deserialize, Serialize)] pub struct Preferences { pub accounts: Vec, pub current_account_index: u32, + pub infinite_scroll: bool, +} + +impl Default for Preferences { + fn default() -> Self { + Self { + accounts: vec![], + current_account_index: 0, + infinite_scroll: true, + } + } } pub fn data_path() -> PathBuf {