diff --git a/src/components/account_row.rs b/src/components/account_row.rs new file mode 100644 index 0000000..e696938 --- /dev/null +++ b/src/components/account_row.rs @@ -0,0 +1,100 @@ +use gtk::prelude::*; +use relm4::prelude::*; + +use crate::settings::{self, Account}; + +use super::accounts_page::AccountsPageInput; + +pub struct AccountRow { + account: Account, + index: usize, +} + +#[derive(Debug)] +pub enum AccountRowInput { + Select, + Logout, + Delete, +} + +#[relm4::factory(pub)] +impl FactoryComponent for AccountRow { + type Init = Account; + type Input = AccountRowInput; + type Output = AccountsPageInput; + type ParentInput = AccountsPageInput; + type ParentWidget = gtk::Box; + type CommandOutput = (); + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + + gtk::Label { + #[watch] + set_label: &format!("{} ({})", self.account.name, self.account.instance_url), + add_controller = gtk::GestureClick { + connect_pressed[sender] => move |_, _, _, _| { + sender.input(AccountRowInput::Select); + } + }, + }, + gtk::Box { + set_hexpand: true, + }, + gtk::Button { + set_icon_name: "system-log-out-symbolic", + connect_clicked => AccountRowInput::Logout, + }, + gtk::Button { + set_icon_name: "edit-delete", + connect_clicked => AccountRowInput::Delete, + set_margin_start: 5, + }, + }, + + gtk::Separator { + set_margin_all: 10, + } + } + } + + fn init_model(init: Self::Init, index: &Self::Index, _sender: FactorySender) -> Self { + Self { + account: init, + index: index.current_index(), + } + } + + fn forward_to_parent(output: Self::Output) -> Option { + Some(output) + } + + fn update(&mut self, message: Self::Input, sender: FactorySender) { + match message { + AccountRowInput::Select => { + settings::update_account_index(self.index); + let message = if self.account.instance_url.is_empty() { + crate::AppMsg::ChooseInstance + } else { + crate::AppMsg::OpenPosts + }; + sender + .output_sender() + .emit(AccountsPageInput::Forward(message)) + } + AccountRowInput::Logout => { + self.account.name = "".to_string(); + self.account.id = 0; + self.account.jwt = None; + settings::update_account(self.account.clone(), self.index); + } + AccountRowInput::Delete => sender + .output_sender() + .emit(AccountsPageInput::Remove(self.index)), + } + } +} diff --git a/src/components/accounts_page.rs b/src/components/accounts_page.rs new file mode 100644 index 0000000..75ad07b --- /dev/null +++ b/src/components/accounts_page.rs @@ -0,0 +1,98 @@ +use gtk::prelude::*; +use relm4::{factory::FactoryVecDeque, prelude::*}; + +use crate::settings::{self, get_prefs, Account}; + +use super::account_row::AccountRow; + +pub struct AccountsPage { + accounts: FactoryVecDeque, +} + +#[derive(Debug)] +pub enum AccountsPageInput { + Update, + CreateNew, + Remove(usize), + Forward(crate::AppMsg), +} + +#[relm4::component(pub)] +impl SimpleComponent for AccountsPage { + type Init = (); + type Input = AccountsPageInput; + type Output = crate::AppMsg; + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + + gtk::Label { + set_label: "Accounts", + add_css_class: "font-very-bold", + set_margin_top: 10, + }, + + gtk::ScrolledWindow { + set_vexpand: true, + + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_vexpand: true, + set_margin_all: 10, + + #[local_ref] + accounts -> gtk::Box { + set_orientation: gtk::Orientation::Vertical, + }, + + gtk::Button { + set_label: "New", + connect_clicked => AccountsPageInput::CreateNew, + set_margin_top: 10, + } + } + } + } + } + + fn init( + _init: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + let accounts = FactoryVecDeque::new(gtk::Box::builder().build(), sender.input_sender()); + sender.input(AccountsPageInput::Update); + + let model = Self { accounts }; + let accounts = model.accounts.widget(); + let widgets = view_output!(); + ComponentParts { model, widgets } + } + + fn update(&mut self, message: Self::Input, sender: ComponentSender) { + match message { + AccountsPageInput::Update => { + self.accounts.guard().clear(); + for account in get_prefs().accounts { + self.accounts.guard().push_back(account); + } + } + AccountsPageInput::CreateNew => { + self.accounts.guard().push_back(Account::default()); + settings::create_account(false); + } + AccountsPageInput::Remove(index) => { + if index as u32 != get_prefs().current_account_index { + self.accounts.guard().remove(index); + settings::remove_account(index); + // for now: update all to fix broken indexes + sender.input(AccountsPageInput::Update); + } + } + AccountsPageInput::Forward(msg) => { + sender.output_sender().emit(msg); + } + } + } +} diff --git a/src/components/instances_page.rs b/src/components/instances_page.rs index 3db1da6..6687a42 100644 --- a/src/components/instances_page.rs +++ b/src/components/instances_page.rs @@ -128,7 +128,7 @@ impl SimpleComponent for InstancesPage { current_account.instance_url = url[0..url.len() - 1].to_string(); current_account.jwt = None; settings::update_current_account(current_account); - crate::AppMsg::ShowLogin + crate::AppMsg::UpdateState(crate::AppState::Login) } Err(err) => crate::AppMsg::ShowMessage(err.to_string()), }; diff --git a/src/components/mod.rs b/src/components/mod.rs index 227c08f..55f024b 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,3 +1,5 @@ +pub mod account_row; +pub mod accounts_page; pub mod comment_row; pub mod communities_page; pub mod community_page; diff --git a/src/main.rs b/src/main.rs index 3853284..be056d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ pub mod util; use api::{community::default_community, post::default_post, user::default_person}; use components::{ + accounts_page::AccountsPage, communities_page::{CommunitiesPage, CommunitiesPageInput}, community_page::{self, CommunityPage}, inbox_page::{InboxInput, InboxPage}, @@ -45,6 +46,7 @@ pub enum AppState { Login, Message, Inbox, + AccountsPage, } struct App { @@ -59,6 +61,7 @@ struct App { post_page: Controller, inbox_page: Controller, login_page: Controller, + accounts_page: Controller, logged_in: bool, about_dialog: Controller, } @@ -66,7 +69,6 @@ struct App { #[derive(Debug, Clone)] pub enum AppMsg { ChooseInstance, - ShowLogin, LoggedIn, Logout, ShowMessage(String), @@ -194,16 +196,22 @@ impl SimpleComponent for App { inbox_page -> gtk::Box {} } } + AppState::AccountsPage => { + gtk::Box { + #[local_ref] + accounts_page -> gtk::Box {} + } + } } } } menu! { menu_model: { - "Choose Instance" => ChangeInstanceAction, - "Profile" => ProfileAction, + "Change Instance" => ChangeInstanceAction, + "Accounts" => AccountsAction, "Login" => LoginAction, - "Logout" => LogoutAction, + "Profile" => ProfileAction, "About" => AboutAction } } @@ -250,6 +258,9 @@ impl SimpleComponent for App { let login_page = LoginPage::builder() .launch(()) .forward(sender.input_sender(), |msg| msg); + let accounts_page = AccountsPage::builder() + .launch(()) + .forward(sender.input_sender(), |msg| msg); let model = App { state, @@ -263,6 +274,7 @@ impl SimpleComponent for App { inbox_page, communities_page, login_page, + accounts_page, message: None, about_dialog, }; @@ -281,6 +293,7 @@ impl SimpleComponent for App { let inbox_page = model.inbox_page.widget(); let communities_page = model.communities_page.widget(); let login_page = model.login_page.widget(); + let accounts_page = model.accounts_page.widget(); let widgets = view_output!(); @@ -290,6 +303,10 @@ impl SimpleComponent for App { RelmAction::new_stateless(move |_| { instance_sender.input(AppMsg::ChooseInstance); }); + let accounts_sender = sender.clone(); + let accounts_action: RelmAction = RelmAction::new_stateless(move |_| { + accounts_sender.input(AppMsg::UpdateState(AppState::AccountsPage)); + }); let profile_sender = sender.clone(); let profile_action: RelmAction = RelmAction::new_stateless(move |_| { let person = settings::get_current_account(); @@ -299,10 +316,7 @@ impl SimpleComponent for App { }); let login_sender = sender.clone(); let login_action: RelmAction = RelmAction::new_stateless(move |_| { - login_sender.input(AppMsg::ShowLogin); - }); - let logout_action: RelmAction = RelmAction::new_stateless(move |_| { - sender.input(AppMsg::Logout); + login_sender.input(AppMsg::UpdateState(AppState::Login)); }); let about_action = { let sender = model.about_dialog.sender().clone(); @@ -313,9 +327,9 @@ impl SimpleComponent for App { let mut group = RelmActionGroup::::new(); group.add_action(instance_action); + group.add_action(accounts_action); group.add_action(profile_action); group.add_action(login_action); - group.add_action(logout_action); group.add_action(about_action); group.register_for_widget(&widgets.main_window); @@ -390,12 +404,11 @@ impl SimpleComponent for App { .emit(post_page::PostPageInput::UpdatePost(post)); self.state = AppState::Post; } - AppMsg::ShowLogin => { - self.state = AppState::Login; - } AppMsg::Logout => { let mut account = settings::get_current_account(); account.jwt = None; + account.name = "".to_string(); + account.id = 0; settings::update_current_account(account); self.logged_in = false; } @@ -434,9 +447,9 @@ impl SimpleComponent for App { relm4::new_action_group!(WindowActionGroup, "win"); relm4::new_stateless_action!(ChangeInstanceAction, WindowActionGroup, "instance"); -relm4::new_stateless_action!(ProfileAction, WindowActionGroup, "profile"); +relm4::new_stateless_action!(AccountsAction, WindowActionGroup, "accounts"); relm4::new_stateless_action!(LoginAction, WindowActionGroup, "login"); -relm4::new_stateless_action!(LogoutAction, WindowActionGroup, "logout"); +relm4::new_stateless_action!(ProfileAction, WindowActionGroup, "profile"); relm4::new_stateless_action!(AboutAction, WindowActionGroup, "about"); fn main() { diff --git a/src/settings.rs b/src/settings.rs index de07e17..6600f4d 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -45,14 +45,40 @@ pub fn get_prefs() -> Preferences { pub fn get_current_account() -> Account { let mut prefs = get_prefs(); if prefs.accounts.len() == 0 { - prefs.accounts.push(Account::default()); - save_prefs(&prefs); + prefs = create_account(true); } prefs.accounts[prefs.current_account_index as usize].clone() } pub fn update_current_account(account: Account) { + let settings = get_prefs(); + update_account(account, settings.current_account_index as usize) +} + +pub fn update_account(account: Account, index: usize) { let mut settings = get_prefs(); - settings.accounts[settings.current_account_index as usize] = account; + settings.accounts[index] = account; save_prefs(&settings); } + +pub fn remove_account(index: usize) { + let mut settings = get_prefs(); + settings.accounts.remove(index); + save_prefs(&settings); +} + +pub fn create_account(reset_index: bool) -> Preferences { + let mut prefs = get_prefs(); + prefs.accounts.push(Account::default()); + if reset_index { + prefs.current_account_index = 0; + } + save_prefs(&prefs); + prefs +} + +pub fn update_account_index(index: usize) { + let mut prefs = get_prefs(); + prefs.current_account_index = index as u32; + save_prefs(&prefs); +}