From 1dc832baea75e676b6462384c4d9429651ad6af4 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Fri, 17 May 2024 11:47:41 -0400 Subject: [PATCH 01/18] app: add accounts to Damus & mock Signed-off-by: kernelkind Signed-off-by: William Casarin --- src/app.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/app.rs b/src/app.rs index d529c8d..4ab30e7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,4 @@ +use crate::account_manager::UserAccount; use crate::app_creation::setup_cc; use crate::app_style::user_requested_visuals_change; use crate::error::Error; @@ -43,6 +44,7 @@ pub struct Damus { pub img_cache: ImageCache, pub ndb: Ndb, + pub accounts: Vec, frame_history: crate::frame_history::FrameHistory, } @@ -644,11 +646,37 @@ impl Damus { timelines, textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), + accounts: Vec::new(), //compose: "".to_string(), frame_history: FrameHistory::default(), } } + pub fn mock>(data_path: P) -> Self { + let mut timelines: Vec = vec![]; + let _initial_limit = 100; + let filter = serde_json::from_str(include_str!("../queries/global.json")).unwrap(); + timelines.push(Timeline::new(filter)); + + let imgcache_dir = data_path.as_ref().join(ImageCache::rel_datadir()); + let _ = std::fs::create_dir_all(imgcache_dir.clone()); + + let mut config = Config::new(); + config.set_ingester_threads(2); + Self { + state: DamusState::Initializing, + pool: RelayPool::new(), + img_cache: ImageCache::new(imgcache_dir), + note_cache: NoteCache::default(), + selected_timeline: 0, + timelines, + textmode: false, + ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), + accounts: Vec::new(), + frame_history: FrameHistory::default(), + } + } + pub fn note_cache_mut(&mut self) -> &mut NoteCache { &mut self.note_cache } From 17d0c97c78d4bcc66585288395b143ea571bbba6 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Fri, 17 May 2024 11:47:42 -0400 Subject: [PATCH 02/18] Create side panel & global popup Create a side panel UI element for desktop with three buttons for: adding a column, settings, and account management. The account management button is temporary pending a better design. It is the only one that is interactable at the moment. When the user clicks it, the global popup window will be shown and the AccountManagementView will be presented on the window. The user can click on the X on the top right of the window to close it. Signed-off-by: kernelkind Signed-off-by: William Casarin --- assets/icons/add_column_dark_4x.png | Bin 0 -> 1150 bytes assets/icons/settings_dark_4x.png | Bin 0 -> 3886 bytes src/ui/account_management.rs | 63 ++++++-------- src/ui/global_popup.rs | 127 ++++++++++++++++++++++++++++ src/ui/mod.rs | 5 ++ src/ui/persist_state.rs | 43 ++++++++++ src/ui/side_panel.rs | 93 ++++++++++++++++++++ src/ui_preview/main.rs | 6 +- 8 files changed, 299 insertions(+), 38 deletions(-) create mode 100644 assets/icons/add_column_dark_4x.png create mode 100644 assets/icons/settings_dark_4x.png create mode 100644 src/ui/global_popup.rs create mode 100644 src/ui/persist_state.rs create mode 100644 src/ui/side_panel.rs diff --git a/assets/icons/add_column_dark_4x.png b/assets/icons/add_column_dark_4x.png new file mode 100644 index 0000000000000000000000000000000000000000..5d8c370b2bd514e372b8c6e5c283528df81da3c0 GIT binary patch literal 1150 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 { account_manager: AccountManager<'a>, simple_preview_controller: SimpleProfilePreviewController<'a>, - edit_mode: &'a mut bool, } impl<'a> View for AccountManagementView<'a> { @@ -28,37 +27,17 @@ impl<'a> AccountManagementView<'a> { pub fn new( account_manager: AccountManager<'a>, simple_preview_controller: SimpleProfilePreviewController<'a>, - edit_mode: &'a mut bool, ) -> Self { AccountManagementView { account_manager, simple_preview_controller, - edit_mode, } } fn show(&mut self, ui: &mut egui::Ui) { - ui.add_space(24.0); - let screen_size = ui.ctx().screen_rect(); - let margin_amt = 128.0; - let window_size = Vec2::new( - screen_size.width() - margin_amt, - screen_size.height() - margin_amt, - ); - - Window::new("Account Management") - .frame(Frame::window(ui.style())) - .collapsible(false) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .resizable(false) - .title_bar(false) - .default_size(window_size) - .show(ui.ctx(), |ui| { - ui.add(title()); - ui.add(self.buttons_widget()); - ui.add_space(8.0); - self.show_accounts(ui); - }); + ui.add(self.buttons_widget()); + ui.add_space(8.0); + self.show_accounts(ui); } fn show_accounts(&mut self, ui: &mut egui::Ui) { @@ -67,7 +46,7 @@ impl<'a> AccountManagementView<'a> { let maybe_remove = self.simple_preview_controller.set_profile_previews( &self.account_manager, ui, - *self.edit_mode, + PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), |ui, preview, edit_mode| { let mut should_remove = false; @@ -103,7 +82,7 @@ impl<'a> AccountManagementView<'a> { let maybe_remove = self.simple_preview_controller.set_profile_previews( &self.account_manager, ui, - *self.edit_mode, + PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), |ui, preview, edit_mode| { let mut should_remove = false; @@ -168,12 +147,12 @@ impl<'a> AccountManagementView<'a> { Vec2::new(ui.available_size_before_wrap().x, 32.0), Layout::left_to_right(egui::Align::Center), |ui| { - if *self.edit_mode { + if PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()) { if ui.add(done_account_button()).clicked() { - *self.edit_mode = false; + PERSISTED_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), false); } } else if ui.add(edit_account_button()).clicked() { - *self.edit_mode = true; + PERSISTED_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), true); } }, ); @@ -193,6 +172,20 @@ impl<'a> AccountManagementView<'a> { } } +impl<'a> FromApp<'a> for AccountManagementView<'a> { + fn from_app(app: &'a mut crate::Damus) -> Self { + // TODO: don't hard-code key store & relay generator + AccountManagementView::new( + AccountManager::new( + &mut app.accounts, + crate::key_storage::KeyStorage::None, + crate::relay_generation::RelayGenerator::Constant, + ), + SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), + ) + } +} + fn simple_preview_frame(ui: &mut egui::Ui) -> Frame { Frame::none() .rounding(ui.visuals().window_rounding) @@ -318,7 +311,6 @@ mod preview { accounts: Vec, ndb: Ndb, img_cache: ImageCache, - edit_mode: bool, } fn get_accounts() -> Vec { @@ -359,7 +351,6 @@ mod preview { accounts, ndb, img_cache, - edit_mode: false, } } } @@ -372,10 +363,10 @@ mod preview { RelayGenerator::Constant, ); + ui.add_space(24.0); AccountManagementView::new( account_manager, SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), - &mut self.edit_mode, ) .ui(ui); } diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs new file mode 100644 index 0000000..e7753b1 --- /dev/null +++ b/src/ui/global_popup.rs @@ -0,0 +1,127 @@ +use egui::{Align2, CentralPanel, RichText, Vec2, Window}; + +use crate::Damus; + +use super::{ + persist_state::{PERSISTED_GLOBAL_POPUP, PERSISTED_SIDE_PANEL}, + AccountManagementView, View, +}; + +#[derive(Clone, Copy, Debug)] +pub enum GlobalPopupType { + AccountManagement, +} + +static ACCOUNT_MANAGEMENT_TITLE: &str = "Account Management"; + +impl GlobalPopupType { + pub fn title(&self) -> &'static str { + match self { + Self::AccountManagement => ACCOUNT_MANAGEMENT_TITLE, + } + } +} + +pub trait FromApp<'a> { + fn from_app(app: &'a mut crate::Damus) -> Self + where + Self: Sized; +} + +fn title(title_str: &'static str) -> RichText { + RichText::new(title_str).size(24.0) +} + +fn overlay_window<'a>( + open: &'a mut bool, + window_size: Vec2, + title_str: &'static str, +) -> Window<'a> { + egui::Window::new(title(title_str)) + .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) + .collapsible(false) + .auto_sized() + .movable(false) + .open(open) + .default_size(window_size) +} + +static MARGIN: Vec2 = Vec2 { x: 100.0, y: 100.0 }; + +pub struct DesktopGlobalPopup<'a> { + app: &'a mut Damus, +} + +impl<'a> View for DesktopGlobalPopup<'a> { + fn ui(&mut self, ui: &mut egui::Ui) { + DesktopGlobalPopup::global_popup(self.app, ui.ctx()) + } +} + +impl<'a> DesktopGlobalPopup<'a> { + pub fn new(app: &'a mut Damus) -> Self { + DesktopGlobalPopup { app } + } + pub fn global_popup(app: &mut Damus, ctx: &egui::Context) { + CentralPanel::default().show(ctx, |ui| { + let available_size = ui.available_size(); + let window_size = available_size - MARGIN; + + if let Some(popup) = PERSISTED_SIDE_PANEL.get_state(ctx) { + let mut show_global_popup = PERSISTED_GLOBAL_POPUP.get_state(ctx); + if show_global_popup { + overlay_window(&mut show_global_popup, window_size, popup.title()).show( + ctx, + |ui| { + match popup { + GlobalPopupType::AccountManagement => { + AccountManagementView::from_app(app).ui(ui) + } + }; + }, + ); + + // user could have closed the window, set the new state in egui memory + PERSISTED_GLOBAL_POPUP.set_state(ctx, show_global_popup); + } + } + }); + } +} + +mod preview { + use crate::{ + ui::{DesktopSidePanel, Preview, View}, + Damus, + }; + + use super::DesktopGlobalPopup; + + pub struct GlobalPopupPreview { + app: Damus, + } + + impl<'a> Preview for DesktopGlobalPopup<'a> { + type Prev = GlobalPopupPreview; + + fn preview() -> Self::Prev { + GlobalPopupPreview::new() + } + } + + impl GlobalPopupPreview { + fn new() -> Self { + GlobalPopupPreview { + app: Damus::mock("."), + } + } + } + + impl View for GlobalPopupPreview { + fn ui(&mut self, ui: &mut egui::Ui) { + let mut panel = DesktopSidePanel::new(ui.ctx()); + DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); + DesktopGlobalPopup::new(&mut self.app).ui(ui); + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 2a04501..ce4caf5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,19 +1,24 @@ pub mod account_login_view; pub mod account_management; pub mod anim; +pub mod global_popup; pub mod mention; pub mod note; +pub mod persist_state; pub mod preview; pub mod profile; pub mod relay; +pub mod side_panel; pub mod username; pub use account_management::{AccountManagementView, AccountSelectionWidget}; +pub use global_popup::DesktopGlobalPopup; pub use mention::Mention; pub use note::Note; pub use preview::{Preview, PreviewApp}; pub use profile::{ProfilePic, ProfilePreview}; pub use relay::RelayView; +pub use side_panel::DesktopSidePanel; pub use username::Username; use egui::Margin; diff --git a/src/ui/persist_state.rs b/src/ui/persist_state.rs new file mode 100644 index 0000000..8a63ac3 --- /dev/null +++ b/src/ui/persist_state.rs @@ -0,0 +1,43 @@ +use egui::util::id_type_map::SerializableAny; + +use super::global_popup::GlobalPopupType; + +/// PersistState is a helper struct for interacting with egui memory persisted data +#[derive(Clone)] +pub struct PersistState { + id: &'static str, + default_state: T, +} + +impl PersistState { + pub fn get_state(&self, ctx: &egui::Context) -> T { + ctx.data_mut(|d| { + d.get_persisted(egui::Id::new(self.id)) + .unwrap_or(self.default_state.clone()) + }) + } + + pub fn set_state(&self, ctx: &egui::Context, new_val: T) { + ctx.data_mut(|d| d.insert_persisted(egui::Id::new(self.id), new_val)); + } +} + +pub static PERSISTED_ACCOUNT_MANAGEMENT: PersistState = PersistState:: { + id: ACCOUNT_MANAGEMENT_VIEW_STATE_ID, + default_state: false, +}; + +pub static PERSISTED_SIDE_PANEL: PersistState> = + PersistState::> { + id: SIDE_PANEL_VIEW_STATE_ID, + default_state: None, + }; + +pub static PERSISTED_GLOBAL_POPUP: PersistState = PersistState:: { + id: GLOBAL_POPUP_VIEW_STATE_ID, + default_state: false, +}; + +static ACCOUNT_MANAGEMENT_VIEW_STATE_ID: &str = "account management view state"; +static SIDE_PANEL_VIEW_STATE_ID: &str = "side panel view state"; +static GLOBAL_POPUP_VIEW_STATE_ID: &str = "global popup view state"; diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs new file mode 100644 index 0000000..0a70f99 --- /dev/null +++ b/src/ui/side_panel.rs @@ -0,0 +1,93 @@ +use egui::{Button, Layout, SidePanel, Vec2}; + +use crate::ui::global_popup::GlobalPopupType; + +use super::{ + persist_state::{PERSISTED_GLOBAL_POPUP, PERSISTED_SIDE_PANEL}, + View, +}; + +pub struct DesktopSidePanel<'a> { + ctx: &'a egui::Context, +} + +static ID: &str = "left panel"; + +impl<'a> View for DesktopSidePanel<'a> { + fn ui(&mut self, ui: &mut egui::Ui) { + DesktopSidePanel::inner(self.ctx, ui); + } +} + +impl<'a> DesktopSidePanel<'a> { + pub fn new(ctx: &'a egui::Context) -> Self { + DesktopSidePanel { ctx } + } + + pub fn inner(ctx: &egui::Context, ui: &mut egui::Ui) { + let dark_mode = ui.ctx().style().visuals.dark_mode; + let spacing_amt = 16.0; + ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| { + ui.add_space(spacing_amt); + if ui + .add_sized(Vec2::new(32.0, 32.0), Button::new("A")) + .clicked() + { + PERSISTED_SIDE_PANEL.set_state(ctx, Some(GlobalPopupType::AccountManagement)); + PERSISTED_GLOBAL_POPUP.set_state(ctx, true); + } + ui.add_space(spacing_amt); + ui.add(settings_button(dark_mode)); + ui.add_space(spacing_amt); + ui.add(add_column_button(dark_mode)); + ui.add_space(spacing_amt); + }); + } + + pub fn panel() -> SidePanel { + egui::SidePanel::left(ID).resizable(false).exact_width(40.0) + } +} + +fn settings_button(dark_mode: bool) -> egui::Button<'static> { + let _ = dark_mode; + let img_data = egui::include_image!("../../assets/icons/settings_dark_4x.png"); + + egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false) +} + +fn add_column_button(dark_mode: bool) -> egui::Button<'static> { + let _ = dark_mode; + let img_data = egui::include_image!("../../assets/icons/add_column_dark_4x.png"); + + egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false) +} + +mod preview { + use crate::ui::Preview; + + use super::*; + + pub struct DesktopSidePanelPreview {} + + impl DesktopSidePanelPreview { + fn new() -> Self { + DesktopSidePanelPreview {} + } + } + + impl View for DesktopSidePanelPreview { + fn ui(&mut self, ui: &mut egui::Ui) { + let mut panel = DesktopSidePanel::new(ui.ctx()); + DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); + } + } + + impl Preview for DesktopSidePanel<'_> { + type Prev = DesktopSidePanelPreview; + + fn preview() -> Self::Prev { + DesktopSidePanelPreview::new() + } + } +} diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs index d2bf9a0..f7c8d06 100644 --- a/src/ui_preview/main.rs +++ b/src/ui_preview/main.rs @@ -3,8 +3,8 @@ use notedeck::app_creation::{ }; use notedeck::ui::account_login_view::AccountLoginView; use notedeck::ui::{ - AccountManagementView, AccountSelectionWidget, Preview, PreviewApp, ProfilePic, ProfilePreview, - RelayView, + AccountManagementView, AccountSelectionWidget, DesktopGlobalPopup, DesktopSidePanel, Preview, + PreviewApp, ProfilePic, ProfilePreview, RelayView, }; use std::env; @@ -88,5 +88,7 @@ async fn main() { ProfilePic, AccountManagementView, AccountSelectionWidget, + DesktopSidePanel, + DesktopGlobalPopup, ); } From 88a3a2d08886258244867dd54d8982ad545fc7da Mon Sep 17 00:00:00 2001 From: kernelkind Date: Fri, 17 May 2024 11:47:43 -0400 Subject: [PATCH 03/18] move test account creation & apply to global popup Signed-off-by: kernelkind Signed-off-by: William Casarin --- src/test_data.rs | 34 +++++++++++++++++++++++++++++++- src/ui/account_management.rs | 38 ++++-------------------------------- src/ui/global_popup.rs | 7 ++++--- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/test_data.rs b/src/test_data.rs index b7d6292..5797e7e 100644 --- a/src/test_data.rs +++ b/src/test_data.rs @@ -1,6 +1,8 @@ -use enostr::RelayPool; +use enostr::{FullKeypair, Pubkey, RelayPool}; use nostrdb::ProfileRecord; +use crate::account_manager::UserAccount; + #[allow(unused_must_use)] pub fn sample_pool() -> RelayPool { let mut pool = RelayPool::new(); @@ -54,3 +56,33 @@ const TEST_PROFILE_DATA: [u8; 448] = [ pub fn test_profile_record() -> ProfileRecord<'static> { ProfileRecord::new_owned(&TEST_PROFILE_DATA).unwrap() } + +const TEN_ACCOUNT_HEXES: [&str; 10] = [ + "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", + "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", + "bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91", + "5c10ed0678805156d39ef1ef6d46110fe1e7e590ae04986ccf48ba1299cb53e2", + "4c96d763eb2fe01910f7e7220b7c7ecdbe1a70057f344b9f79c28af080c3ee30", + "edf16b1dd61eab353a83af470cc13557029bff6827b4cb9b7fc9bdb632a2b8e6", + "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", + "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", + "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", + "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", +]; + +pub fn get_test_accounts() -> Vec { + TEN_ACCOUNT_HEXES + .iter() + .map(|account_hex| { + let key = FullKeypair::new( + Pubkey::from_hex(account_hex).unwrap(), + FullKeypair::generate().secret_key, + ); + + UserAccount { + key, + relays: sample_pool(), + } + }) + .collect() +} diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 4197688..f5ef146 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -285,51 +285,21 @@ impl<'a> AccountSelectionWidget<'a> { // PREVIEWS mod preview { - use enostr::{FullKeypair, Pubkey}; use nostrdb::{Config, Ndb}; use super::*; + use crate::imgcache::ImageCache; use crate::key_storage::KeyStorage; use crate::relay_generation::RelayGenerator; - use crate::{imgcache::ImageCache, test_data}; + use crate::test_data; use std::path::Path; - const ACCOUNT_HEXES: [&str; 10] = [ - "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", - "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", - "bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91", - "5c10ed0678805156d39ef1ef6d46110fe1e7e590ae04986ccf48ba1299cb53e2", - "4c96d763eb2fe01910f7e7220b7c7ecdbe1a70057f344b9f79c28af080c3ee30", - "edf16b1dd61eab353a83af470cc13557029bff6827b4cb9b7fc9bdb632a2b8e6", - "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", - "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", - "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", - "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", - ]; - pub struct AccountManagementPreview { accounts: Vec, ndb: Ndb, img_cache: ImageCache, } - fn get_accounts() -> Vec { - ACCOUNT_HEXES - .iter() - .map(|account_hex| { - let key = FullKeypair::new( - Pubkey::from_hex(account_hex).unwrap(), - FullKeypair::generate().secret_key, - ); - - UserAccount { - key, - relays: test_data::sample_pool(), - } - }) - .collect() - } - fn get_ndb_and_img_cache() -> (Ndb, ImageCache) { let mut config = Config::new(); config.set_ingester_threads(2); @@ -344,7 +314,7 @@ mod preview { impl AccountManagementPreview { fn new() -> Self { - let accounts = get_accounts(); + let accounts = test_data::get_test_accounts(); let (ndb, img_cache) = get_ndb_and_img_cache(); AccountManagementPreview { @@ -388,7 +358,7 @@ mod preview { impl AccountSelectionPreview { fn new() -> Self { - let accounts = get_accounts(); + let accounts = test_data::get_test_accounts(); let (ndb, img_cache) = get_ndb_and_img_cache(); AccountSelectionPreview { accounts, diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs index e7753b1..994fb6d 100644 --- a/src/ui/global_popup.rs +++ b/src/ui/global_popup.rs @@ -91,6 +91,7 @@ impl<'a> DesktopGlobalPopup<'a> { mod preview { use crate::{ + test_data::get_test_accounts, ui::{DesktopSidePanel, Preview, View}, Damus, }; @@ -111,9 +112,9 @@ mod preview { impl GlobalPopupPreview { fn new() -> Self { - GlobalPopupPreview { - app: Damus::mock("."), - } + let mut app = Damus::mock("."); + app.accounts = get_test_accounts(); + GlobalPopupPreview { app } } } From fd943e5f9f65cc398a50499b2917c03826fbc4e0 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Fri, 17 May 2024 11:47:44 -0400 Subject: [PATCH 04/18] account_management: refactor Signed-off-by: kernelkind Signed-off-by: William Casarin --- src/ui/account_management.rs | 160 ++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 77 deletions(-) diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index f5ef146..96a06cf 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -6,7 +6,9 @@ use crate::{ }; use egui::{Align, Button, Frame, Id, Layout, Margin, RichText, ScrollArea, Sense, Vec2}; +use super::global_popup::GlobalPopupType; use super::persist_state::PERSISTED_ACCOUNT_MANAGEMENT; +use super::profile::preview::SimpleProfilePreview; pub struct AccountManagementView<'a> { account_manager: AccountManager<'a>, @@ -37,88 +39,41 @@ impl<'a> AccountManagementView<'a> { fn show(&mut self, ui: &mut egui::Ui) { ui.add(self.buttons_widget()); ui.add_space(8.0); - self.show_accounts(ui); + scroll_area().show(ui, |ui| { + self.show_accounts(ui); + }); } fn show_accounts(&mut self, ui: &mut egui::Ui) { - scroll_area().show(ui, |ui| { - ui.horizontal_wrapped(|ui| { - let maybe_remove = self.simple_preview_controller.set_profile_previews( - &self.account_manager, - ui, - PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), - |ui, preview, edit_mode| { - let mut should_remove = false; + ui.horizontal_wrapped(|ui| { + let maybe_remove = self.simple_preview_controller.set_profile_previews( + &self.account_manager, + ui, + PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), + desktop_account_card_ui(), + ); - ui.add_sized(preview.dimensions(), |ui: &mut egui::Ui| { - simple_preview_frame(ui) - .show(ui, |ui| { - ui.vertical_centered(|ui| { - ui.add(preview); - if edit_mode { - should_remove = ui - .add(delete_button(ui.visuals().dark_mode)) - .clicked(); - } - }); - }) - .response - }); - should_remove - }, - ); - - self.maybe_remove_accounts(maybe_remove); - }); + self.maybe_remove_accounts(maybe_remove); }); } fn show_accounts_mobile(&mut self, ui: &mut egui::Ui) { - scroll_area().show(ui, |ui| { - ui.allocate_ui_with_layout( - Vec2::new(ui.available_size_before_wrap().x, 32.0), - Layout::top_down(egui::Align::Min), - |ui| { - let maybe_remove = self.simple_preview_controller.set_profile_previews( - &self.account_manager, - ui, - PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), - |ui, preview, edit_mode| { - let mut should_remove = false; + ui.allocate_ui_with_layout( + Vec2::new(ui.available_size_before_wrap().x, 32.0), + Layout::top_down(egui::Align::Min), + |ui| { + // create all account 'cards' and get the indicies the user requested to remove + let maybe_remove = self.simple_preview_controller.set_profile_previews( + &self.account_manager, + ui, + PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), + mobile_account_card_ui(), // closure for creating an account 'card' + ); - ui.add_sized( - Vec2::new(ui.available_width(), 50.0), - |ui: &mut egui::Ui| { - Frame::none() - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.add(preview); - if edit_mode { - ui.with_layout( - Layout::right_to_left(Align::Center), - |ui| { - should_remove = ui - .add(delete_button( - ui.visuals().dark_mode, - )) - .clicked(); - }, - ); - } - }); - }) - .response - }, - ); - ui.add_space(16.0); - should_remove - }, - ); - - self.maybe_remove_accounts(maybe_remove); - }, - ); - }); + // remove all account indicies user requested + self.maybe_remove_accounts(maybe_remove); + }, + ); } fn maybe_remove_accounts(&mut self, account_indices: Option>) { @@ -132,10 +87,12 @@ impl<'a> AccountManagementView<'a> { fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response { egui::CentralPanel::default() .show(ui.ctx(), |ui| { - ui.add(title()); + ui.add(mobile_title()); ui.add(self.buttons_widget()); ui.add_space(8.0); - self.show_accounts_mobile(ui); + scroll_area().show(ui, |ui| { + self.show_accounts_mobile(ui); + }); }) .response } @@ -172,6 +129,55 @@ impl<'a> AccountManagementView<'a> { } } +fn mobile_account_card_ui( +) -> fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, edit_mode: bool) -> bool { + |ui, preview, edit_mode| { + let mut should_remove = false; + + ui.add_sized( + Vec2::new(ui.available_width(), 50.0), + |ui: &mut egui::Ui| { + Frame::none() + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.add(preview); + if edit_mode { + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + should_remove = + ui.add(delete_button(ui.visuals().dark_mode)).clicked(); + }); + } + }); + }) + .response + }, + ); + ui.add_space(16.0); + should_remove + } +} + +fn desktop_account_card_ui( +) -> fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, edit_mode: bool) -> bool { + |ui: &mut egui::Ui, preview, edit_mode| { + let mut should_remove = false; + + ui.add_sized(preview.dimensions(), |ui: &mut egui::Ui| { + simple_preview_frame(ui) + .show(ui, |ui| { + ui.vertical_centered(|ui| { + ui.add(preview); + if edit_mode { + should_remove = ui.add(delete_button(ui.visuals().dark_mode)).clicked(); + } + }); + }) + .response + }); + should_remove + } +} + impl<'a> FromApp<'a> for AccountManagementView<'a> { fn from_app(app: &'a mut crate::Damus) -> Self { // TODO: don't hard-code key store & relay generator @@ -195,11 +201,11 @@ fn simple_preview_frame(ui: &mut egui::Ui) -> Frame { .inner_margin(12.0) } -fn title() -> impl egui::Widget { +fn mobile_title() -> impl egui::Widget { |ui: &mut egui::Ui| { ui.vertical_centered(|ui| { ui.label( - RichText::new("Accounts") + RichText::new(GlobalPopupType::AccountManagement.title()) .text_style(NotedeckTextStyle::Heading2.text_style()) .strong(), ); From 66ce42a3026b5374b3f03c6e7785def1d2d3828e Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 18 May 2024 10:57:28 -0700 Subject: [PATCH 05/18] remove context from DesktopSidePanel we can just get this from the egui::Ui when rendering Signed-off-by: William Casarin --- src/ui/global_popup.rs | 2 +- src/ui/side_panel.rs | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs index 994fb6d..0580c6c 100644 --- a/src/ui/global_popup.rs +++ b/src/ui/global_popup.rs @@ -120,7 +120,7 @@ mod preview { impl View for GlobalPopupPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let mut panel = DesktopSidePanel::new(ui.ctx()); + let mut panel = DesktopSidePanel::new(); DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); DesktopGlobalPopup::new(&mut self.app).ui(ui); } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs index 0a70f99..a46058d 100644 --- a/src/ui/side_panel.rs +++ b/src/ui/side_panel.rs @@ -7,24 +7,23 @@ use super::{ View, }; -pub struct DesktopSidePanel<'a> { - ctx: &'a egui::Context, -} +#[derive(Default)] +pub struct DesktopSidePanel {} static ID: &str = "left panel"; -impl<'a> View for DesktopSidePanel<'a> { +impl View for DesktopSidePanel { fn ui(&mut self, ui: &mut egui::Ui) { - DesktopSidePanel::inner(self.ctx, ui); + DesktopSidePanel::inner(ui); } } -impl<'a> DesktopSidePanel<'a> { - pub fn new(ctx: &'a egui::Context) -> Self { - DesktopSidePanel { ctx } +impl DesktopSidePanel { + pub fn new() -> Self { + DesktopSidePanel::default() } - pub fn inner(ctx: &egui::Context, ui: &mut egui::Ui) { + pub fn inner(ui: &mut egui::Ui) { let dark_mode = ui.ctx().style().visuals.dark_mode; let spacing_amt = 16.0; ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| { @@ -33,8 +32,8 @@ impl<'a> DesktopSidePanel<'a> { .add_sized(Vec2::new(32.0, 32.0), Button::new("A")) .clicked() { - PERSISTED_SIDE_PANEL.set_state(ctx, Some(GlobalPopupType::AccountManagement)); - PERSISTED_GLOBAL_POPUP.set_state(ctx, true); + PERSISTED_SIDE_PANEL.set_state(ui.ctx(), Some(GlobalPopupType::AccountManagement)); + PERSISTED_GLOBAL_POPUP.set_state(ui.ctx(), true); } ui.add_space(spacing_amt); ui.add(settings_button(dark_mode)); @@ -78,12 +77,12 @@ mod preview { impl View for DesktopSidePanelPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let mut panel = DesktopSidePanel::new(ui.ctx()); + let mut panel = DesktopSidePanel::new(); DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); } } - impl Preview for DesktopSidePanel<'_> { + impl Preview for DesktopSidePanel { type Prev = DesktopSidePanelPreview; fn preview() -> Self::Prev { From 194f41d39c7e701a61acf9d1f7ece2e40739a395 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Sat, 18 May 2024 10:58:26 -0700 Subject: [PATCH 06/18] integrate sidebar into main view buttons don't do anything yet Signed-off-by: William Casarin --- src/app.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app.rs b/src/app.rs index 4ab30e7..61507c1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -859,9 +859,12 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) { fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: usize) { StripBuilder::new(ui) + .size(Size::exact(40.0)) .sizes(sizes, timelines) .clip(true) .horizontal(|mut strip| { + strip.cell(crate::ui::DesktopSidePanel::inner); + for timeline_ind in 0..timelines { strip.cell(|ui| timeline::timeline_view(ui, app, timeline_ind)); } From f071d59dae15fab8611683c58b5d36fbb285de8f Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 22 May 2024 15:25:58 -0400 Subject: [PATCH 07/18] Rename PersistState -> StateInMemory also use IdTypeMap::insert_temp instead of insert_persisted. The whole conception of using egui memory to share state is probably going to be changed to a more robust solution in the future. Signed-off-by: kernelkind --- src/ui/account_management.rs | 12 +++++------ src/ui/global_popup.rs | 8 ++++---- src/ui/mod.rs | 2 +- src/ui/side_panel.rs | 6 +++--- .../{persist_state.rs => state_in_memory.rs} | 20 +++++++++---------- 5 files changed, 24 insertions(+), 24 deletions(-) rename src/ui/{persist_state.rs => state_in_memory.rs} (54%) diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 96a06cf..98dba2e 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -7,8 +7,8 @@ use crate::{ use egui::{Align, Button, Frame, Id, Layout, Margin, RichText, ScrollArea, Sense, Vec2}; use super::global_popup::GlobalPopupType; -use super::persist_state::PERSISTED_ACCOUNT_MANAGEMENT; use super::profile::preview::SimpleProfilePreview; +use super::state_in_memory::STATE_ACCOUNT_MANAGEMENT; pub struct AccountManagementView<'a> { account_manager: AccountManager<'a>, @@ -49,7 +49,7 @@ impl<'a> AccountManagementView<'a> { let maybe_remove = self.simple_preview_controller.set_profile_previews( &self.account_manager, ui, - PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), + STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), desktop_account_card_ui(), ); @@ -66,7 +66,7 @@ impl<'a> AccountManagementView<'a> { let maybe_remove = self.simple_preview_controller.set_profile_previews( &self.account_manager, ui, - PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), + STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), mobile_account_card_ui(), // closure for creating an account 'card' ); @@ -104,12 +104,12 @@ impl<'a> AccountManagementView<'a> { Vec2::new(ui.available_size_before_wrap().x, 32.0), Layout::left_to_right(egui::Align::Center), |ui| { - if PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()) { + if STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()) { if ui.add(done_account_button()).clicked() { - PERSISTED_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), false); + STATE_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), false); } } else if ui.add(edit_account_button()).clicked() { - PERSISTED_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), true); + STATE_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), true); } }, ); diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs index 0580c6c..2901796 100644 --- a/src/ui/global_popup.rs +++ b/src/ui/global_popup.rs @@ -3,7 +3,7 @@ use egui::{Align2, CentralPanel, RichText, Vec2, Window}; use crate::Damus; use super::{ - persist_state::{PERSISTED_GLOBAL_POPUP, PERSISTED_SIDE_PANEL}, + state_in_memory::{STATE_GLOBAL_POPUP, STATE_SIDE_PANEL}, AccountManagementView, View, }; @@ -67,8 +67,8 @@ impl<'a> DesktopGlobalPopup<'a> { let available_size = ui.available_size(); let window_size = available_size - MARGIN; - if let Some(popup) = PERSISTED_SIDE_PANEL.get_state(ctx) { - let mut show_global_popup = PERSISTED_GLOBAL_POPUP.get_state(ctx); + if let Some(popup) = STATE_SIDE_PANEL.get_state(ctx) { + let mut show_global_popup = STATE_GLOBAL_POPUP.get_state(ctx); if show_global_popup { overlay_window(&mut show_global_popup, window_size, popup.title()).show( ctx, @@ -82,7 +82,7 @@ impl<'a> DesktopGlobalPopup<'a> { ); // user could have closed the window, set the new state in egui memory - PERSISTED_GLOBAL_POPUP.set_state(ctx, show_global_popup); + STATE_GLOBAL_POPUP.set_state(ctx, show_global_popup); } } }); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ce4caf5..5f05bd0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -4,11 +4,11 @@ pub mod anim; pub mod global_popup; pub mod mention; pub mod note; -pub mod persist_state; pub mod preview; pub mod profile; pub mod relay; pub mod side_panel; +pub mod state_in_memory; pub mod username; pub use account_management::{AccountManagementView, AccountSelectionWidget}; diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs index a46058d..2c9bb71 100644 --- a/src/ui/side_panel.rs +++ b/src/ui/side_panel.rs @@ -3,7 +3,7 @@ use egui::{Button, Layout, SidePanel, Vec2}; use crate::ui::global_popup::GlobalPopupType; use super::{ - persist_state::{PERSISTED_GLOBAL_POPUP, PERSISTED_SIDE_PANEL}, + state_in_memory::{STATE_GLOBAL_POPUP, STATE_SIDE_PANEL}, View, }; @@ -32,8 +32,8 @@ impl DesktopSidePanel { .add_sized(Vec2::new(32.0, 32.0), Button::new("A")) .clicked() { - PERSISTED_SIDE_PANEL.set_state(ui.ctx(), Some(GlobalPopupType::AccountManagement)); - PERSISTED_GLOBAL_POPUP.set_state(ui.ctx(), true); + STATE_SIDE_PANEL.set_state(ui.ctx(), Some(GlobalPopupType::AccountManagement)); + STATE_GLOBAL_POPUP.set_state(ui.ctx(), true); } ui.add_space(spacing_amt); ui.add(settings_button(dark_mode)); diff --git a/src/ui/persist_state.rs b/src/ui/state_in_memory.rs similarity index 54% rename from src/ui/persist_state.rs rename to src/ui/state_in_memory.rs index 8a63ac3..7823cb8 100644 --- a/src/ui/persist_state.rs +++ b/src/ui/state_in_memory.rs @@ -1,39 +1,39 @@ -use egui::util::id_type_map::SerializableAny; +use std::any::Any; use super::global_popup::GlobalPopupType; -/// PersistState is a helper struct for interacting with egui memory persisted data +/// StateInMemory is a helper struct for interacting with egui memory persisted data #[derive(Clone)] -pub struct PersistState { +pub struct StateInMemory { id: &'static str, default_state: T, } -impl PersistState { +impl StateInMemory { pub fn get_state(&self, ctx: &egui::Context) -> T { ctx.data_mut(|d| { - d.get_persisted(egui::Id::new(self.id)) + d.get_temp(egui::Id::new(self.id)) .unwrap_or(self.default_state.clone()) }) } pub fn set_state(&self, ctx: &egui::Context, new_val: T) { - ctx.data_mut(|d| d.insert_persisted(egui::Id::new(self.id), new_val)); + ctx.data_mut(|d| d.insert_temp(egui::Id::new(self.id), new_val)); } } -pub static PERSISTED_ACCOUNT_MANAGEMENT: PersistState = PersistState:: { +pub static STATE_ACCOUNT_MANAGEMENT: StateInMemory = StateInMemory:: { id: ACCOUNT_MANAGEMENT_VIEW_STATE_ID, default_state: false, }; -pub static PERSISTED_SIDE_PANEL: PersistState> = - PersistState::> { +pub static STATE_SIDE_PANEL: StateInMemory> = + StateInMemory::> { id: SIDE_PANEL_VIEW_STATE_ID, default_state: None, }; -pub static PERSISTED_GLOBAL_POPUP: PersistState = PersistState:: { +pub static STATE_GLOBAL_POPUP: StateInMemory = StateInMemory:: { id: GLOBAL_POPUP_VIEW_STATE_ID, default_state: false, }; From 748d9d2358fedd35a65986d005ed99a13a805ad9 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 22 May 2024 15:32:58 -0400 Subject: [PATCH 08/18] Integrate global popup into app Signed-off-by: kernelkind --- src/app.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 61507c1..30770e5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use crate::imgcache::ImageCache; use crate::notecache::{CachedNote, NoteCache}; use crate::timeline; use crate::timeline::{NoteRef, Timeline, ViewFilter}; -use crate::ui::is_mobile; +use crate::ui::{is_mobile, DesktopGlobalPopup, DesktopSidePanel, View}; use crate::Result; use egui::{Context, Frame, Style}; @@ -838,7 +838,11 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) { }; if app.timelines.len() == 1 { + DesktopSidePanel::panel().show(ctx, |ui| { + DesktopSidePanel::inner(ui); + }); main_panel(&ctx.style()).show(ctx, |ui| { + DesktopGlobalPopup::new(app).ui(ui); timeline::timeline_view(ui, app, 0); }); @@ -847,6 +851,7 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) { main_panel(&ctx.style()).show(ctx, |ui| { ui.spacing_mut().item_spacing.x = 0.0; + DesktopGlobalPopup::new(app).ui(ui); if need_scroll { egui::ScrollArea::horizontal().show(ui, |ui| { timelines_view(ui, panel_sizes, app, app.timelines.len()); From 11b3effa511ed97cda7df086f6f7ee939e3e1c6d Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 22 May 2024 16:27:23 -0400 Subject: [PATCH 09/18] Add AccountManager to app Signed-off-by: kernelkind --- src/account_manager.rs | 41 +++++++++++++++++++-------- src/app.rs | 19 ++++++++++--- src/relay_generation.rs | 30 ++++++++++++-------- src/ui/account_management.rs | 55 ++++++++++++++++-------------------- src/ui/global_popup.rs | 7 +++-- 5 files changed, 93 insertions(+), 59 deletions(-) diff --git a/src/account_manager.rs b/src/account_manager.rs index c8bbc9f..c0d8284 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -19,7 +19,7 @@ impl<'a> SimpleProfilePreviewController<'a> { pub fn set_profile_previews( &mut self, - account_manager: &AccountManager<'a>, + account_manager: &AccountManager, ui: &mut egui::Ui, edit_mode: bool, add_preview_ui: fn( @@ -56,7 +56,7 @@ impl<'a> SimpleProfilePreviewController<'a> { pub fn view_profile_previews( &mut self, - account_manager: &'a AccountManager<'a>, + account_manager: &'a AccountManager, ui: &mut egui::Ui, add_preview_ui: fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, index: usize) -> bool, ) -> Option { @@ -86,18 +86,31 @@ impl<'a> SimpleProfilePreviewController<'a> { /// The interface for managing the user's accounts. /// Represents all user-facing operations related to account management. -pub struct AccountManager<'a> { - accounts: &'a mut Vec, +pub struct AccountManager { + accounts: Vec, key_store: KeyStorage, relay_generator: RelayGenerator, } -impl<'a> AccountManager<'a> { +impl AccountManager { pub fn new( - accounts: &'a mut Vec, key_store: KeyStorage, + // TODO: right now, there is only one way of generating relays for all accounts. In the future + // each account should have the option of generating relays differently relay_generator: RelayGenerator, + wakeup: impl Fn() + Send + Sync + Clone + 'static, ) -> Self { + let accounts = if let Ok(keys) = key_store.get_keys() { + keys.into_iter() + .map(|key| { + let relays = relay_generator.generate_relays_for(&key.pubkey, wakeup.clone()); + UserAccount { key, relays } + }) + .collect() + } else { + Vec::new() + }; + AccountManager { accounts, key_store, @@ -105,11 +118,11 @@ impl<'a> AccountManager<'a> { } } - pub fn get_accounts(&'a self) -> &'a Vec { - self.accounts + pub fn get_accounts(&self) -> &Vec { + &self.accounts } - pub fn get_account(&'a self, index: usize) -> Option<&'a UserAccount> { + pub fn get_account(&self, index: usize) -> Option<&UserAccount> { self.accounts.get(index) } @@ -122,9 +135,15 @@ impl<'a> AccountManager<'a> { } } - pub fn add_account(&'a mut self, key: FullKeypair, ctx: &egui::Context) { + pub fn add_account( + &mut self, + key: FullKeypair, + wakeup: impl Fn() + Send + Sync + Clone + 'static, + ) { let _ = self.key_store.add_key(&key); - let relays = self.relay_generator.generate_relays_for(&key.pubkey, ctx); + let relays = self + .relay_generator + .generate_relays_for(&key.pubkey, wakeup); let account = UserAccount { key, relays }; self.accounts.push(account) diff --git a/src/app.rs b/src/app.rs index 30770e5..db73eb9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,10 +1,11 @@ -use crate::account_manager::UserAccount; +use crate::account_manager::AccountManager; use crate::app_creation::setup_cc; use crate::app_style::user_requested_visuals_change; use crate::error::Error; use crate::frame_history::FrameHistory; use crate::imgcache::ImageCache; use crate::notecache::{CachedNote, NoteCache}; +use crate::relay_pool_manager::create_wakeup; use crate::timeline; use crate::timeline::{NoteRef, Timeline, ViewFilter}; use crate::ui::{is_mobile, DesktopGlobalPopup, DesktopSidePanel, View}; @@ -44,7 +45,7 @@ pub struct Damus { pub img_cache: ImageCache, pub ndb: Ndb, - pub accounts: Vec, + pub account_manager: AccountManager, frame_history: crate::frame_history::FrameHistory, } @@ -646,7 +647,13 @@ impl Damus { timelines, textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), - accounts: Vec::new(), + account_manager: AccountManager::new( + // TODO: use correct KeyStorage mechanism for current OS arch + crate::key_storage::KeyStorage::None, + // TODO: setting for relay generator + crate::relay_generation::RelayGenerator::Constant, + create_wakeup(&cc.egui_ctx), + ), //compose: "".to_string(), frame_history: FrameHistory::default(), } @@ -672,7 +679,11 @@ impl Damus { timelines, textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), - accounts: Vec::new(), + account_manager: AccountManager::new( + crate::key_storage::KeyStorage::None, + crate::relay_generation::RelayGenerator::Constant, + || {}, + ), frame_history: FrameHistory::default(), } } diff --git a/src/relay_generation.rs b/src/relay_generation.rs index 20df1be..8bd33a6 100644 --- a/src/relay_generation.rs +++ b/src/relay_generation.rs @@ -1,4 +1,3 @@ -use crate::relay_pool_manager::create_wakeup; use enostr::{Pubkey, RelayPool}; use tracing::error; @@ -9,30 +8,39 @@ pub enum RelayGenerator { } impl RelayGenerator { - pub fn generate_relays_for(&self, key: &Pubkey, ctx: &egui::Context) -> RelayPool { + pub fn generate_relays_for( + &self, + key: &Pubkey, + wakeup: impl Fn() + Send + Sync + Clone + 'static, + ) -> RelayPool { match self { - Self::GossipModel => generate_relays_gossip(key, ctx), - Self::Nip65 => generate_relays_nip65(key, ctx), - Self::Constant => generate_constant_relays(ctx), + Self::GossipModel => generate_relays_gossip(key, wakeup), + Self::Nip65 => generate_relays_nip65(key, wakeup), + Self::Constant => generate_constant_relays(wakeup), } } } -fn generate_relays_gossip(key: &Pubkey, ctx: &egui::Context) -> RelayPool { - let _ = ctx; +fn generate_relays_gossip( + key: &Pubkey, + wakeup: impl Fn() + Send + Sync + Clone + 'static, +) -> RelayPool { + let _ = wakeup; let _ = key; todo!() } -fn generate_relays_nip65(key: &Pubkey, ctx: &egui::Context) -> RelayPool { - let _ = ctx; +fn generate_relays_nip65( + key: &Pubkey, + wakeup: impl Fn() + Send + Sync + Clone + 'static, +) -> RelayPool { + let _ = wakeup; let _ = key; todo!() } -fn generate_constant_relays(ctx: &egui::Context) -> RelayPool { +fn generate_constant_relays(wakeup: impl Fn() + Send + Sync + Clone + 'static) -> RelayPool { let mut pool = RelayPool::new(); - let wakeup = create_wakeup(ctx); if let Err(e) = pool.add_url("ws://localhost:8080".to_string(), wakeup.clone()) { error!("{:?}", e) diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 98dba2e..4b2e001 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -11,7 +11,7 @@ use super::profile::preview::SimpleProfilePreview; use super::state_in_memory::STATE_ACCOUNT_MANAGEMENT; pub struct AccountManagementView<'a> { - account_manager: AccountManager<'a>, + account_manager: &'a mut AccountManager, simple_preview_controller: SimpleProfilePreviewController<'a>, } @@ -27,7 +27,7 @@ impl<'a> View for AccountManagementView<'a> { impl<'a> AccountManagementView<'a> { pub fn new( - account_manager: AccountManager<'a>, + account_manager: &'a mut AccountManager, simple_preview_controller: SimpleProfilePreviewController<'a>, ) -> Self { AccountManagementView { @@ -47,7 +47,7 @@ impl<'a> AccountManagementView<'a> { fn show_accounts(&mut self, ui: &mut egui::Ui) { ui.horizontal_wrapped(|ui| { let maybe_remove = self.simple_preview_controller.set_profile_previews( - &self.account_manager, + self.account_manager, ui, STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), desktop_account_card_ui(), @@ -64,7 +64,7 @@ impl<'a> AccountManagementView<'a> { |ui| { // create all account 'cards' and get the indicies the user requested to remove let maybe_remove = self.simple_preview_controller.set_profile_previews( - &self.account_manager, + self.account_manager, ui, STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), mobile_account_card_ui(), // closure for creating an account 'card' @@ -180,13 +180,8 @@ fn desktop_account_card_ui( impl<'a> FromApp<'a> for AccountManagementView<'a> { fn from_app(app: &'a mut crate::Damus) -> Self { - // TODO: don't hard-code key store & relay generator AccountManagementView::new( - AccountManager::new( - &mut app.accounts, - crate::key_storage::KeyStorage::None, - crate::relay_generation::RelayGenerator::Constant, - ), + &mut app.account_manager, SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), ) } @@ -239,7 +234,7 @@ fn delete_button(_dark_mode: bool) -> egui::Button<'static> { } pub struct AccountSelectionWidget<'a> { - account_manager: AccountManager<'a>, + account_manager: &'a mut AccountManager, simple_preview_controller: SimpleProfilePreviewController<'a>, } @@ -249,7 +244,7 @@ impl<'a> AccountSelectionWidget<'a> { scroll_area().show(ui, |ui| { ui.horizontal_wrapped(|ui| { let clicked_at = self.simple_preview_controller.view_profile_previews( - &self.account_manager, + self.account_manager, ui, |ui, preview, index| { let resp = ui.add_sized(preview.dimensions(), |ui: &mut egui::Ui| { @@ -278,7 +273,7 @@ impl<'a> AccountSelectionWidget<'a> { impl<'a> AccountSelectionWidget<'a> { pub fn new( - account_manager: AccountManager<'a>, + account_manager: &'a mut AccountManager, simple_preview_controller: SimpleProfilePreviewController<'a>, ) -> Self { AccountSelectionWidget { @@ -301,7 +296,7 @@ mod preview { use std::path::Path; pub struct AccountManagementPreview { - accounts: Vec, + account_manager: AccountManager, ndb: Ndb, img_cache: ImageCache, } @@ -320,11 +315,16 @@ mod preview { impl AccountManagementPreview { fn new() -> Self { + let mut account_manager = + AccountManager::new(KeyStorage::None, RelayGenerator::Constant, || {}); let accounts = test_data::get_test_accounts(); + accounts + .into_iter() + .for_each(|acc| account_manager.add_account(acc.key, || {})); let (ndb, img_cache) = get_ndb_and_img_cache(); AccountManagementPreview { - accounts, + account_manager, ndb, img_cache, } @@ -333,15 +333,9 @@ mod preview { impl View for AccountManagementPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let account_manager = AccountManager::new( - &mut self.accounts, - KeyStorage::None, - RelayGenerator::Constant, - ); - ui.add_space(24.0); AccountManagementView::new( - account_manager, + &mut self.account_manager, SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), ) .ui(ui); @@ -357,17 +351,22 @@ mod preview { } pub struct AccountSelectionPreview { - accounts: Vec, + account_manager: AccountManager, ndb: Ndb, img_cache: ImageCache, } impl AccountSelectionPreview { fn new() -> Self { + let mut account_manager = + AccountManager::new(KeyStorage::None, RelayGenerator::Constant, || {}); let accounts = test_data::get_test_accounts(); + accounts + .into_iter() + .for_each(|acc| account_manager.add_account(acc.key, || {})); let (ndb, img_cache) = get_ndb_and_img_cache(); AccountSelectionPreview { - accounts, + account_manager, ndb, img_cache, } @@ -376,14 +375,8 @@ mod preview { impl View for AccountSelectionPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let account_manager = AccountManager::new( - &mut self.accounts, - KeyStorage::None, - RelayGenerator::Constant, - ); - let mut widget = AccountSelectionWidget::new( - account_manager, + &mut self.account_manager, SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), ); diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs index 2901796..b0355d8 100644 --- a/src/ui/global_popup.rs +++ b/src/ui/global_popup.rs @@ -91,7 +91,7 @@ impl<'a> DesktopGlobalPopup<'a> { mod preview { use crate::{ - test_data::get_test_accounts, + test_data, ui::{DesktopSidePanel, Preview, View}, Damus, }; @@ -113,7 +113,10 @@ mod preview { impl GlobalPopupPreview { fn new() -> Self { let mut app = Damus::mock("."); - app.accounts = get_test_accounts(); + let accounts = test_data::get_test_accounts(); + accounts + .into_iter() + .for_each(|acc| app.account_manager.add_account(acc.key, || {})); GlobalPopupPreview { app } } } From bdf6156fff7f99da12aab1709615a0388318162c Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 22 May 2024 16:35:17 -0400 Subject: [PATCH 10/18] Move preview controller out of account_manager.rs Signed-off-by: kernelkind --- src/account_manager.rs | 83 +------------------- src/ui/account_management.rs | 3 +- src/ui/profile/mod.rs | 2 + src/ui/profile/profile_preview_controller.rs | 82 +++++++++++++++++++ 4 files changed, 87 insertions(+), 83 deletions(-) create mode 100644 src/ui/profile/profile_preview_controller.rs diff --git a/src/account_manager.rs b/src/account_manager.rs index c0d8284..0c03d99 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -1,88 +1,7 @@ use enostr::FullKeypair; -use nostrdb::{Ndb, Transaction}; pub use crate::user_account::UserAccount; -use crate::{ - imgcache::ImageCache, key_storage::KeyStorage, relay_generation::RelayGenerator, - ui::profile::preview::SimpleProfilePreview, -}; - -pub struct SimpleProfilePreviewController<'a> { - ndb: &'a Ndb, - img_cache: &'a mut ImageCache, -} - -impl<'a> SimpleProfilePreviewController<'a> { - pub fn new(ndb: &'a Ndb, img_cache: &'a mut ImageCache) -> Self { - SimpleProfilePreviewController { ndb, img_cache } - } - - pub fn set_profile_previews( - &mut self, - account_manager: &AccountManager, - ui: &mut egui::Ui, - edit_mode: bool, - add_preview_ui: fn( - ui: &mut egui::Ui, - preview: SimpleProfilePreview, - edit_mode: bool, - ) -> bool, - ) -> Option> { - let mut to_remove: Option> = None; - - for i in 0..account_manager.num_accounts() { - if let Some(account) = account_manager.get_account(i) { - if let Ok(txn) = Transaction::new(self.ndb) { - let profile = self - .ndb - .get_profile_by_pubkey(&txn, account.key.pubkey.bytes()); - - if let Ok(profile) = profile { - let preview = SimpleProfilePreview::new(&profile, self.img_cache); - - if add_preview_ui(ui, preview, edit_mode) { - if to_remove.is_none() { - to_remove = Some(Vec::new()); - } - to_remove.as_mut().unwrap().push(i); - } - }; - } - } - } - - to_remove - } - - pub fn view_profile_previews( - &mut self, - account_manager: &'a AccountManager, - ui: &mut egui::Ui, - add_preview_ui: fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, index: usize) -> bool, - ) -> Option { - let mut clicked_at: Option = None; - - for i in 0..account_manager.num_accounts() { - if let Some(account) = account_manager.get_account(i) { - if let Ok(txn) = Transaction::new(self.ndb) { - let profile = self - .ndb - .get_profile_by_pubkey(&txn, account.key.pubkey.bytes()); - - if let Ok(profile) = profile { - let preview = SimpleProfilePreview::new(&profile, self.img_cache); - - if add_preview_ui(ui, preview, i) { - clicked_at = Some(i) - } - } - } - } - } - - clicked_at - } -} +use crate::{key_storage::KeyStorage, relay_generation::RelayGenerator}; /// The interface for managing the user's accounts. /// Represents all user-facing operations related to account management. diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 4b2e001..5ae98ee 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -1,6 +1,6 @@ use crate::ui::global_popup::FromApp; use crate::{ - account_manager::{AccountManager, SimpleProfilePreviewController, UserAccount}, + account_manager::{AccountManager, UserAccount}, app_style::NotedeckTextStyle, ui::{self, Preview, View}, }; @@ -8,6 +8,7 @@ use egui::{Align, Button, Frame, Id, Layout, Margin, RichText, ScrollArea, Sense use super::global_popup::GlobalPopupType; use super::profile::preview::SimpleProfilePreview; +use super::profile::SimpleProfilePreviewController; use super::state_in_memory::STATE_ACCOUNT_MANAGEMENT; pub struct AccountManagementView<'a> { diff --git a/src/ui/profile/mod.rs b/src/ui/profile/mod.rs index dafd7b3..c388add 100644 --- a/src/ui/profile/mod.rs +++ b/src/ui/profile/mod.rs @@ -1,5 +1,7 @@ pub mod picture; pub mod preview; +mod profile_preview_controller; pub use picture::ProfilePic; pub use preview::ProfilePreview; +pub use profile_preview_controller::SimpleProfilePreviewController; diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs new file mode 100644 index 0000000..8eb3a94 --- /dev/null +++ b/src/ui/profile/profile_preview_controller.rs @@ -0,0 +1,82 @@ +use nostrdb::{Ndb, Transaction}; + +use crate::{account_manager::AccountManager, imgcache::ImageCache}; + +use super::preview::SimpleProfilePreview; + +pub struct SimpleProfilePreviewController<'a> { + ndb: &'a Ndb, + img_cache: &'a mut ImageCache, +} + +impl<'a> SimpleProfilePreviewController<'a> { + pub fn new(ndb: &'a Ndb, img_cache: &'a mut ImageCache) -> Self { + SimpleProfilePreviewController { ndb, img_cache } + } + + pub fn set_profile_previews( + &mut self, + account_manager: &AccountManager, + ui: &mut egui::Ui, + edit_mode: bool, + add_preview_ui: fn( + ui: &mut egui::Ui, + preview: SimpleProfilePreview, + edit_mode: bool, + ) -> bool, + ) -> Option> { + let mut to_remove: Option> = None; + + for i in 0..account_manager.num_accounts() { + if let Some(account) = account_manager.get_account(i) { + if let Ok(txn) = Transaction::new(self.ndb) { + let profile = self + .ndb + .get_profile_by_pubkey(&txn, account.key.pubkey.bytes()); + + if let Ok(profile) = profile { + let preview = SimpleProfilePreview::new(&profile, self.img_cache); + + if add_preview_ui(ui, preview, edit_mode) { + if to_remove.is_none() { + to_remove = Some(Vec::new()); + } + to_remove.as_mut().unwrap().push(i); + } + }; + } + } + } + + to_remove + } + + pub fn view_profile_previews( + &mut self, + account_manager: &'a AccountManager, + ui: &mut egui::Ui, + add_preview_ui: fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, index: usize) -> bool, + ) -> Option { + let mut clicked_at: Option = None; + + for i in 0..account_manager.num_accounts() { + if let Some(account) = account_manager.get_account(i) { + if let Ok(txn) = Transaction::new(self.ndb) { + let profile = self + .ndb + .get_profile_by_pubkey(&txn, account.key.pubkey.bytes()); + + if let Ok(profile) = profile { + let preview = SimpleProfilePreview::new(&profile, self.img_cache); + + if add_preview_ui(ui, preview, i) { + clicked_at = Some(i) + } + } + } + } + } + + clicked_at + } +} From 2ca47edf4d90188525a8b496d5efd6228b74952e Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 22 May 2024 16:40:52 -0400 Subject: [PATCH 11/18] AccountManager: add ability to make a selection Signed-off-by: kernelkind --- src/account_manager.rs | 13 +++++++++++++ src/app.rs | 3 +++ src/ui/account_management.rs | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/account_manager.rs b/src/account_manager.rs index 0c03d99..21fafee 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -6,6 +6,7 @@ use crate::{key_storage::KeyStorage, relay_generation::RelayGenerator}; /// The interface for managing the user's accounts. /// Represents all user-facing operations related to account management. pub struct AccountManager { + currently_selected_account: Option, accounts: Vec, key_store: KeyStorage, relay_generator: RelayGenerator, @@ -13,6 +14,7 @@ pub struct AccountManager { impl AccountManager { pub fn new( + currently_selected_account: Option, key_store: KeyStorage, // TODO: right now, there is only one way of generating relays for all accounts. In the future // each account should have the option of generating relays differently @@ -31,6 +33,7 @@ impl AccountManager { }; AccountManager { + currently_selected_account, accounts, key_store, relay_generator, @@ -71,4 +74,14 @@ impl AccountManager { pub fn num_accounts(&self) -> usize { self.accounts.len() } + + pub fn get_currently_selected_account(&self) -> Option { + self.currently_selected_account + } + + pub fn select_account(&mut self, index: usize) { + if self.accounts.get(index).is_some() { + self.currently_selected_account = Some(index) + } + } } diff --git a/src/app.rs b/src/app.rs index db73eb9..52993ee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -648,6 +648,8 @@ impl Damus { textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), account_manager: AccountManager::new( + // TODO: should pull this from settings + None, // TODO: use correct KeyStorage mechanism for current OS arch crate::key_storage::KeyStorage::None, // TODO: setting for relay generator @@ -680,6 +682,7 @@ impl Damus { textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), account_manager: AccountManager::new( + None, crate::key_storage::KeyStorage::None, crate::relay_generation::RelayGenerator::Constant, || {}, diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 5ae98ee..4d86cac 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -317,7 +317,7 @@ mod preview { impl AccountManagementPreview { fn new() -> Self { let mut account_manager = - AccountManager::new(KeyStorage::None, RelayGenerator::Constant, || {}); + AccountManager::new(None, KeyStorage::None, RelayGenerator::Constant, || {}); let accounts = test_data::get_test_accounts(); accounts .into_iter() @@ -360,7 +360,7 @@ mod preview { impl AccountSelectionPreview { fn new() -> Self { let mut account_manager = - AccountManager::new(KeyStorage::None, RelayGenerator::Constant, || {}); + AccountManager::new(None, KeyStorage::None, RelayGenerator::Constant, || {}); let accounts = test_data::get_test_accounts(); accounts .into_iter() From f489ed3b9e816d16f2fd49a131f65f8963add9b8 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 22 May 2024 17:25:06 -0400 Subject: [PATCH 12/18] Migrate to new AccountManagementView conception Signed-off-by: kernelkind --- assets/icons/add_account_icon_4x.png | Bin 0 -> 3081 bytes assets/icons/select_icon_3x.png | Bin 0 -> 1405 bytes assets/icons/signout_icon_4x.png | Bin 0 -> 1420 bytes src/account_manager.rs | 20 +- src/ui/account_management.rs | 183 ++++++++++--------- src/ui/global_popup.rs | 2 +- src/ui/profile/mod.rs | 2 +- src/ui/profile/profile_preview_controller.rs | 37 +++- 8 files changed, 150 insertions(+), 94 deletions(-) create mode 100644 assets/icons/add_account_icon_4x.png create mode 100644 assets/icons/select_icon_3x.png create mode 100644 assets/icons/signout_icon_4x.png diff --git a/assets/icons/add_account_icon_4x.png b/assets/icons/add_account_icon_4x.png new file mode 100644 index 0000000000000000000000000000000000000000..1f11a71c3115c4b781068c7e0829f9789920a81e GIT binary patch literal 3081 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 a+`=!ohTfO2dL*p?0000 { + self.select_account(selected_index - 1); + } + Ordering::Equal => { + self.clear_selected_account(); + } + Ordering::Less => {} + } + } } } @@ -84,4 +96,8 @@ impl AccountManager { self.currently_selected_account = Some(index) } } + + pub fn clear_selected_account(&mut self) { + self.currently_selected_account = None + } } diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 4d86cac..974e0ef 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -4,12 +4,13 @@ use crate::{ app_style::NotedeckTextStyle, ui::{self, Preview, View}, }; -use egui::{Align, Button, Frame, Id, Layout, Margin, RichText, ScrollArea, Sense, Vec2}; +use egui::{ + Align, Button, Color32, Frame, Id, Image, Layout, Margin, RichText, ScrollArea, Sense, Vec2, +}; use super::global_popup::GlobalPopupType; use super::profile::preview::SimpleProfilePreview; -use super::profile::SimpleProfilePreviewController; -use super::state_in_memory::STATE_ACCOUNT_MANAGEMENT; +use super::profile::{ProfilePreviewOp, SimpleProfilePreviewController}; pub struct AccountManagementView<'a> { account_manager: &'a mut AccountManager, @@ -38,24 +39,23 @@ impl<'a> AccountManagementView<'a> { } fn show(&mut self, ui: &mut egui::Ui) { - ui.add(self.buttons_widget()); - ui.add_space(8.0); - scroll_area().show(ui, |ui| { - self.show_accounts(ui); + Frame::none().outer_margin(24.0).show(ui, |ui| { + ui.add(self.top_section_buttons_widget()); + ui.add_space(8.0); + scroll_area().show(ui, |ui| { + self.show_accounts(ui); + }); }); } fn show_accounts(&mut self, ui: &mut egui::Ui) { - ui.horizontal_wrapped(|ui| { - let maybe_remove = self.simple_preview_controller.set_profile_previews( - self.account_manager, - ui, - STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), - desktop_account_card_ui(), - ); + let maybe_remove = self.simple_preview_controller.set_profile_previews( + self.account_manager, + ui, + account_card_ui(), + ); - self.maybe_remove_accounts(maybe_remove); - }); + self.maybe_remove_accounts(maybe_remove); } fn show_accounts_mobile(&mut self, ui: &mut egui::Ui) { @@ -67,8 +67,7 @@ impl<'a> AccountManagementView<'a> { let maybe_remove = self.simple_preview_controller.set_profile_previews( self.account_manager, ui, - STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), - mobile_account_card_ui(), // closure for creating an account 'card' + account_card_ui(), // closure for creating an account 'card' ); // remove all account indicies user requested @@ -89,7 +88,7 @@ impl<'a> AccountManagementView<'a> { egui::CentralPanel::default() .show(ui.ctx(), |ui| { ui.add(mobile_title()); - ui.add(self.buttons_widget()); + ui.add(self.top_section_buttons_widget()); ui.add_space(8.0); scroll_area().show(ui, |ui| { self.show_accounts_mobile(ui); @@ -98,84 +97,73 @@ impl<'a> AccountManagementView<'a> { .response } - fn buttons_widget(&mut self) -> impl egui::Widget + '_ { + fn top_section_buttons_widget(&mut self) -> impl egui::Widget + '_ { |ui: &mut egui::Ui| { ui.horizontal(|ui| { ui.allocate_ui_with_layout( Vec2::new(ui.available_size_before_wrap().x, 32.0), Layout::left_to_right(egui::Align::Center), - |ui| { - if STATE_ACCOUNT_MANAGEMENT.get_state(ui.ctx()) { - if ui.add(done_account_button()).clicked() { - STATE_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), false); - } - } else if ui.add(edit_account_button()).clicked() { - STATE_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), true); - } - }, - ); - - ui.allocate_ui_with_layout( - Vec2::new(ui.available_size_before_wrap().x, 32.0), - Layout::right_to_left(egui::Align::Center), |ui| { if ui.add(add_account_button()).clicked() { // TODO: route to AccountLoginView } }, ); + + // UNCOMMENT FOR LOGOUTALL BUTTON + // ui.allocate_ui_with_layout( + // Vec2::new(ui.available_size_before_wrap().x, 32.0), + // Layout::right_to_left(egui::Align::Center), + // |ui| { + // if ui.add(logout_all_button()).clicked() { + // for index in (0..self.account_manager.num_accounts()).rev() { + // self.account_manager.remove_account(index); + // } + // } + // }, + // ); }) .response } } } -fn mobile_account_card_ui( -) -> fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, edit_mode: bool) -> bool { - |ui, preview, edit_mode| { - let mut should_remove = false; +fn account_card_ui() -> fn( + ui: &mut egui::Ui, + preview: SimpleProfilePreview, + width: f32, + is_selected: bool, +) -> Option { + |ui, preview, width, is_selected| { + let mut op: Option = None; - ui.add_sized( - Vec2::new(ui.available_width(), 50.0), - |ui: &mut egui::Ui| { - Frame::none() - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.add(preview); - if edit_mode { - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - should_remove = - ui.add(delete_button(ui.visuals().dark_mode)).clicked(); - }); + ui.add_sized(Vec2::new(width, 50.0), |ui: &mut egui::Ui| { + Frame::none() + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.add(preview); + + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if is_selected { + ui.add(selected_widget()); + } else { + if ui + .add(switch_button(ui.style().visuals.dark_mode)) + .clicked() + { + op = Some(ProfilePreviewOp::SwitchTo); + } + if ui.add(sign_out_button(ui)).clicked() { + op = Some(ProfilePreviewOp::RemoveAccount) + } } }); - }) - .response - }, - ); - ui.add_space(16.0); - should_remove - } -} - -fn desktop_account_card_ui( -) -> fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, edit_mode: bool) -> bool { - |ui: &mut egui::Ui, preview, edit_mode| { - let mut should_remove = false; - - ui.add_sized(preview.dimensions(), |ui: &mut egui::Ui| { - simple_preview_frame(ui) - .show(ui, |ui| { - ui.vertical_centered(|ui| { - ui.add(preview); - if edit_mode { - should_remove = ui.add(delete_button(ui.visuals().dark_mode)).clicked(); - } }); }) .response }); - should_remove + ui.add_space(16.0); + op } } @@ -216,24 +204,55 @@ fn scroll_area() -> ScrollArea { .auto_shrink([false; 2]) } +static PINK: Color32 = Color32::from_rgb(0xE4, 0x5A, 0xC9); + fn add_account_button() -> Button<'static> { - Button::new("Add Account").min_size(Vec2::new(0.0, 32.0)) + let img_data = egui::include_image!("../../assets/icons/add_account_icon_4x.png"); + let img = Image::new(img_data).fit_to_exact_size(Vec2::new(48.0, 48.0)); + Button::image_and_text( + img, + RichText::new(" Add account") + .size(16.0) + // TODO: this color should not be hard coded. Find some way to add it to the visuals + .color(PINK), + ) + .frame(false) } -fn edit_account_button() -> Button<'static> { - Button::new("Edit").min_size(Vec2::new(0.0, 32.0)) +fn sign_out_button(ui: &egui::Ui) -> egui::Button<'static> { + let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png"); + let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); + + egui::Button::image_and_text( + img, + RichText::new("Sign out").color(ui.visuals().noninteractive().fg_stroke.color), + ) + .frame(false) } -fn done_account_button() -> Button<'static> { - Button::new("Done").min_size(Vec2::new(0.0, 32.0)) +fn switch_button(dark_mode: bool) -> egui::Button<'static> { + let _ = dark_mode; + + egui::Button::new("Switch").min_size(Vec2::new(76.0, 32.0)) } -fn delete_button(_dark_mode: bool) -> egui::Button<'static> { - let img_data = egui::include_image!("../../assets/icons/delete_icon_4x.png"); - - egui::Button::image(egui::Image::new(img_data).max_width(30.0)).frame(true) +fn selected_widget() -> impl egui::Widget { + |ui: &mut egui::Ui| { + Frame::none() + .show(ui, |ui| { + ui.label(RichText::new("Selected").size(13.0).color(PINK)); + let img_data = egui::include_image!("../../assets/icons/select_icon_3x.png"); + let img = Image::new(img_data).max_size(Vec2::new(16.0, 16.0)); + ui.add(img); + }) + .response + } } +// fn logout_all_button() -> egui::Button<'static> { +// egui::Button::new("Logout all") +// } + pub struct AccountSelectionWidget<'a> { account_manager: &'a mut AccountManager, simple_preview_controller: SimpleProfilePreviewController<'a>, diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs index b0355d8..75edd85 100644 --- a/src/ui/global_popup.rs +++ b/src/ui/global_popup.rs @@ -12,7 +12,7 @@ pub enum GlobalPopupType { AccountManagement, } -static ACCOUNT_MANAGEMENT_TITLE: &str = "Account Management"; +static ACCOUNT_MANAGEMENT_TITLE: &str = "Manage accounts"; impl GlobalPopupType { pub fn title(&self) -> &'static str { diff --git a/src/ui/profile/mod.rs b/src/ui/profile/mod.rs index c388add..e67b2d0 100644 --- a/src/ui/profile/mod.rs +++ b/src/ui/profile/mod.rs @@ -4,4 +4,4 @@ mod profile_preview_controller; pub use picture::ProfilePic; pub use preview::ProfilePreview; -pub use profile_preview_controller::SimpleProfilePreviewController; +pub use profile_preview_controller::{ProfilePreviewOp, SimpleProfilePreviewController}; diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs index 8eb3a94..a6e93fb 100644 --- a/src/ui/profile/profile_preview_controller.rs +++ b/src/ui/profile/profile_preview_controller.rs @@ -9,6 +9,12 @@ pub struct SimpleProfilePreviewController<'a> { img_cache: &'a mut ImageCache, } +#[derive(Debug)] +pub enum ProfilePreviewOp { + RemoveAccount, + SwitchTo, +} + impl<'a> SimpleProfilePreviewController<'a> { pub fn new(ndb: &'a Ndb, img_cache: &'a mut ImageCache) -> Self { SimpleProfilePreviewController { ndb, img_cache } @@ -16,17 +22,19 @@ impl<'a> SimpleProfilePreviewController<'a> { pub fn set_profile_previews( &mut self, - account_manager: &AccountManager, + account_manager: &mut AccountManager, ui: &mut egui::Ui, - edit_mode: bool, add_preview_ui: fn( ui: &mut egui::Ui, preview: SimpleProfilePreview, - edit_mode: bool, - ) -> bool, + width: f32, + is_selected: bool, + ) -> Option, ) -> Option> { let mut to_remove: Option> = None; + let width = ui.available_width(); + for i in 0..account_manager.num_accounts() { if let Some(account) = account_manager.get_account(i) { if let Ok(txn) = Transaction::new(self.ndb) { @@ -37,11 +45,24 @@ impl<'a> SimpleProfilePreviewController<'a> { if let Ok(profile) = profile { let preview = SimpleProfilePreview::new(&profile, self.img_cache); - if add_preview_ui(ui, preview, edit_mode) { - if to_remove.is_none() { - to_remove = Some(Vec::new()); + let is_selected = if let Some(selected) = + account_manager.get_currently_selected_account() + { + i == selected + } else { + false + }; + + if let Some(op) = add_preview_ui(ui, preview, width, is_selected) { + match op { + ProfilePreviewOp::RemoveAccount => { + if to_remove.is_none() { + to_remove = Some(Vec::new()); + } + to_remove.as_mut().unwrap().push(i); + } + ProfilePreviewOp::SwitchTo => account_manager.select_account(i), } - to_remove.as_mut().unwrap().push(i); } }; } From c0b1a01b5daf6b72dacf195bab36656dd5699738 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Fri, 24 May 2024 16:46:37 -0400 Subject: [PATCH 13/18] Account switcher Signed-off-by: kernelkind --- assets/icons/plus_icon_4x.png | Bin 0 -> 340 bytes src/account_manager.rs | 14 +- src/colors.rs | 2 + src/ui/account_management.rs | 78 +------- src/ui/account_switcher.rs | 187 +++++++++++++++++++ src/ui/mod.rs | 4 +- src/ui/profile/preview.rs | 8 +- src/ui/profile/profile_preview_controller.rs | 59 ++++-- 8 files changed, 257 insertions(+), 95 deletions(-) create mode 100644 assets/icons/plus_icon_4x.png create mode 100644 src/ui/account_switcher.rs diff --git a/assets/icons/plus_icon_4x.png b/assets/icons/plus_icon_4x.png new file mode 100644 index 0000000000000000000000000000000000000000..97ebadf7861d24ed0dc5a67e03f5b31c1ec68aa4 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!1|;QLq8Nb`XMsm#F;L7Kgc+CGPFo2SWGoJH zcVbv~PUa<$!;&U>cv7h@-A}f&p($mE;q=ND74PUON0EyOz#ktJ72NtNByh^`j zrF0@JVeJClEzMkqwq4ZOCz6-JNk+SD(tOt0YBzVoYVQ<;ez&p9XuZIE}y<( zG4tRIb(gZbEBhBRO?i+~#Kn+5S=x2lzr-DGo^l#{u3k^^>EN2sbn$QDZ)xorZp}AM zX80l#Me2RWP59 Option { + pub fn get_selected_account_index(&self) -> Option { self.currently_selected_account } + pub fn get_selected_account(&self) -> Option<&UserAccount> { + if let Some(account_index) = self.currently_selected_account { + if let Some(account) = self.get_account(account_index) { + Some(account) + } else { + None + } + } else { + None + } + } + pub fn select_account(&mut self, index: usize) { if self.accounts.get(index).is_some() { self.currently_selected_account = Some(index) diff --git a/src/colors.rs b/src/colors.rs index bd4d08a..afb0e2b 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,6 +1,8 @@ use egui::Color32; pub const PURPLE: Color32 = Color32::from_rgb(0xCC, 0x43, 0xC5); +// TODO: This should not be exposed publicly +pub const PINK: Color32 = Color32::from_rgb(0xE4, 0x5A, 0xC9); //pub const DARK_BG: Color32 = egui::Color32::from_rgb(40, 44, 52); pub const GRAY_SECONDARY: Color32 = Color32::from_rgb(0x8A, 0x8A, 0x8A); const BLACK: Color32 = Color32::from_rgb(0x00, 0x00, 0x00); diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 974e0ef..06b3a27 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -1,12 +1,11 @@ +use crate::colors::PINK; use crate::ui::global_popup::FromApp; use crate::{ - account_manager::{AccountManager, UserAccount}, + account_manager::AccountManager, app_style::NotedeckTextStyle, ui::{self, Preview, View}, }; -use egui::{ - Align, Button, Color32, Frame, Id, Image, Layout, Margin, RichText, ScrollArea, Sense, Vec2, -}; +use egui::{Align, Button, Frame, Image, Layout, RichText, ScrollArea, Vec2}; use super::global_popup::GlobalPopupType; use super::profile::preview::SimpleProfilePreview; @@ -176,15 +175,6 @@ impl<'a> FromApp<'a> for AccountManagementView<'a> { } } -fn simple_preview_frame(ui: &mut egui::Ui) -> Frame { - Frame::none() - .rounding(ui.visuals().window_rounding) - .fill(ui.visuals().window_fill) - .stroke(ui.visuals().window_stroke) - .outer_margin(Margin::same(2.0)) - .inner_margin(12.0) -} - fn mobile_title() -> impl egui::Widget { |ui: &mut egui::Ui| { ui.vertical_centered(|ui| { @@ -204,8 +194,6 @@ fn scroll_area() -> ScrollArea { .auto_shrink([false; 2]) } -static PINK: Color32 = Color32::from_rgb(0xE4, 0x5A, 0xC9); - fn add_account_button() -> Button<'static> { let img_data = egui::include_image!("../../assets/icons/add_account_icon_4x.png"); let img = Image::new(img_data).fit_to_exact_size(Vec2::new(48.0, 48.0)); @@ -253,60 +241,11 @@ fn selected_widget() -> impl egui::Widget { // egui::Button::new("Logout all") // } -pub struct AccountSelectionWidget<'a> { - account_manager: &'a mut AccountManager, - simple_preview_controller: SimpleProfilePreviewController<'a>, -} - -impl<'a> AccountSelectionWidget<'a> { - fn ui(&'a mut self, ui: &mut egui::Ui) -> Option<&'a UserAccount> { - let mut result: Option<&'a UserAccount> = None; - scroll_area().show(ui, |ui| { - ui.horizontal_wrapped(|ui| { - let clicked_at = self.simple_preview_controller.view_profile_previews( - self.account_manager, - ui, - |ui, preview, index| { - let resp = ui.add_sized(preview.dimensions(), |ui: &mut egui::Ui| { - simple_preview_frame(ui) - .show(ui, |ui| { - ui.vertical_centered(|ui| { - ui.add(preview); - }); - }) - .response - }); - - ui.interact(resp.rect, Id::new(index), Sense::click()) - .clicked() - }, - ); - - if let Some(index) = clicked_at { - result = self.account_manager.get_account(index); - }; - }); - }); - result - } -} - -impl<'a> AccountSelectionWidget<'a> { - pub fn new( - account_manager: &'a mut AccountManager, - simple_preview_controller: SimpleProfilePreviewController<'a>, - ) -> Self { - AccountSelectionWidget { - account_manager, - simple_preview_controller, - } - } -} - // PREVIEWS mod preview { use nostrdb::{Config, Ndb}; + use ui::account_switcher::AccountSelectionWidget; use super::*; use crate::imgcache::ImageCache; @@ -395,14 +334,11 @@ mod preview { impl View for AccountSelectionPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let mut widget = AccountSelectionWidget::new( + AccountSelectionWidget::new( &mut self.account_manager, SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), - ); - - if let Some(account) = widget.ui(ui) { - println!("User made selection: {:?}", account.key); - } + ) + .ui(ui); } } diff --git a/src/ui/account_switcher.rs b/src/ui/account_switcher.rs new file mode 100644 index 0000000..034c22f --- /dev/null +++ b/src/ui/account_switcher.rs @@ -0,0 +1,187 @@ +use crate::{account_manager::UserAccount, colors::PINK, ui}; +use egui::{ + Align, Button, Color32, Frame, Id, Image, Layout, Margin, RichText, Rounding, ScrollArea, + Sense, Vec2, +}; + +use crate::account_manager::AccountManager; + +use super::profile::{preview::SimpleProfilePreview, SimpleProfilePreviewController}; + +pub struct AccountSelectionWidget<'a> { + account_manager: &'a mut AccountManager, + simple_preview_controller: SimpleProfilePreviewController<'a>, +} + +impl<'a> AccountSelectionWidget<'a> { + pub fn ui(&'a mut self, ui: &mut egui::Ui) { + if ui::is_mobile() { + self.show_mobile(ui); + } else { + self.show(ui); + } + } + + fn show(&mut self, ui: &mut egui::Ui) { + Frame::none().outer_margin(8.0).show(ui, |ui| { + ui.add(top_section_widget()); + scroll_area().show(ui, |ui| { + self.show_accounts(ui); + }); + ui.add_space(8.0); + ui.add(add_account_button()); + + if let Some(account_index) = self.account_manager.get_selected_account_index() { + ui.add_space(8.0); + if self.handle_sign_out(ui, account_index) { + self.account_manager.remove_account(account_index); + } + } + + ui.add_space(8.0); + }); + } + + fn handle_sign_out(&mut self, ui: &mut egui::Ui, account_index: usize) -> bool { + if let Some(account) = self.account_manager.get_account(account_index) { + if let Some(response) = self.sign_out_button(ui, account) { + return response.clicked(); + } + } + false + } + + fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response { + let _ = ui; + todo!() + } + + fn show_accounts(&mut self, ui: &mut egui::Ui) { + self.simple_preview_controller.view_profile_previews( + self.account_manager, + ui, + account_switcher_card_ui(), + ); + } + fn sign_out_button(&self, ui: &mut egui::Ui, account: &UserAccount) -> Option { + self.simple_preview_controller.show_with_nickname( + ui, + &account.key.pubkey, + |ui, username| { + let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png"); + let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); + let button = egui::Button::image_and_text( + img, + RichText::new(format!(" Sign out @{}", username.username())) + .color(PINK) + .size(16.0), + ) + .frame(false); + + ui.add(button) + }, + ) + } +} + +impl<'a> AccountSelectionWidget<'a> { + pub fn new( + account_manager: &'a mut AccountManager, + simple_preview_controller: SimpleProfilePreviewController<'a>, + ) -> Self { + AccountSelectionWidget { + account_manager, + simple_preview_controller, + } + } +} + +fn account_switcher_card_ui() -> fn( + ui: &mut egui::Ui, + preview: SimpleProfilePreview, + width: f32, + is_selected: bool, + index: usize, +) -> bool { + |ui, preview, width, is_selected, index| { + let resp = ui.add_sized(Vec2::new(width, 50.0), |ui: &mut egui::Ui| { + Frame::none() + .show(ui, |ui| { + ui.add_space(8.0); + ui.horizontal(|ui| { + if is_selected { + Frame::none() + .rounding(Rounding::same(8.0)) + .inner_margin(Margin::same(8.0)) + .fill(Color32::from_rgb(0x45, 0x1B, 0x59)) + .show(ui, |ui| { + ui.add(preview); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + ui.add(selection_widget()); + }); + }); + } else { + ui.add_space(8.0); + ui.add(preview); + } + }); + }) + .response + }); + + ui.interact(resp.rect, Id::new(index), Sense::click()) + .clicked() + } +} + +fn selection_widget() -> impl egui::Widget { + |ui: &mut egui::Ui| { + let img_data: egui::ImageSource = + egui::include_image!("../../assets/icons/select_icon_3x.png"); + let img = Image::new(img_data).max_size(Vec2::new(16.0, 16.0)); + ui.add(img) + } +} + +fn top_section_widget() -> impl egui::Widget { + |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + ui.allocate_ui_with_layout( + Vec2::new(ui.available_size_before_wrap().x, 32.0), + Layout::left_to_right(egui::Align::Center), + |ui| ui.add(account_switcher_title()), + ); + + ui.allocate_ui_with_layout( + Vec2::new(ui.available_size_before_wrap().x, 32.0), + Layout::right_to_left(egui::Align::Center), + |ui| { + if ui.add(manage_accounts_button()).clicked() { + // TODO: route to AccountLoginView + } + }, + ); + }) + .response + } +} + +fn manage_accounts_button() -> egui::Button<'static> { + Button::new(RichText::new("Manage").color(PINK).size(16.0)).frame(false) +} + +fn account_switcher_title() -> impl egui::Widget { + |ui: &mut egui::Ui| ui.label(RichText::new("Account switcher").size(20.0).strong()) +} + +fn scroll_area() -> ScrollArea { + egui::ScrollArea::vertical() + .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) + .auto_shrink([false; 2]) +} + +fn add_account_button() -> egui::Button<'static> { + let img_data = egui::include_image!("../../assets/icons/plus_icon_4x.png"); + let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); + Button::image_and_text(img, RichText::new(" Add account").size(16.0).color(PINK)).frame(false) +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 5f05bd0..d4bd5e4 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,5 +1,6 @@ pub mod account_login_view; pub mod account_management; +pub mod account_switcher; pub mod anim; pub mod global_popup; pub mod mention; @@ -11,7 +12,8 @@ pub mod side_panel; pub mod state_in_memory; pub mod username; -pub use account_management::{AccountManagementView, AccountSelectionWidget}; +pub use account_management::AccountManagementView; +pub use account_switcher::AccountSelectionWidget; pub use global_popup::DesktopGlobalPopup; pub use mention::Mention; pub use note::Note; diff --git a/src/ui/profile/preview.rs b/src/ui/profile/preview.rs index 860a7f6..3d851ae 100644 --- a/src/ui/profile/preview.rs +++ b/src/ui/profile/preview.rs @@ -3,7 +3,7 @@ use crate::imgcache::ImageCache; use crate::ui::ProfilePic; use crate::{colors, images, DisplayName}; use egui::load::TexturePoll; -use egui::{Frame, RichText, Sense, Vec2, Widget}; +use egui::{Frame, RichText, Sense, Widget}; use egui_extras::Size; use nostrdb::ProfileRecord; @@ -93,10 +93,6 @@ impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut ImageCache) -> Self { SimpleProfilePreview { profile, cache } } - - pub fn dimensions(&self) -> Vec2 { - Vec2::new(120.0, 150.0) - } } impl<'a, 'cache> egui::Widget for SimpleProfilePreview<'a, 'cache> { @@ -152,7 +148,7 @@ mod previews { } } -fn get_display_name<'a>(profile: &'a ProfileRecord<'a>) -> DisplayName<'a> { +pub fn get_display_name<'a>(profile: &'a ProfileRecord<'a>) -> DisplayName<'a> { if let Some(name) = crate::profile::get_profile_name(profile) { name } else { diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs index a6e93fb..15e9e25 100644 --- a/src/ui/profile/profile_preview_controller.rs +++ b/src/ui/profile/profile_preview_controller.rs @@ -1,8 +1,9 @@ +use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use crate::{account_manager::AccountManager, imgcache::ImageCache}; +use crate::{account_manager::AccountManager, imgcache::ImageCache, DisplayName}; -use super::preview::SimpleProfilePreview; +use super::preview::{get_display_name, SimpleProfilePreview}; pub struct SimpleProfilePreviewController<'a> { ndb: &'a Ndb, @@ -45,13 +46,12 @@ impl<'a> SimpleProfilePreviewController<'a> { if let Ok(profile) = profile { let preview = SimpleProfilePreview::new(&profile, self.img_cache); - let is_selected = if let Some(selected) = - account_manager.get_currently_selected_account() - { - i == selected - } else { - false - }; + let is_selected = + if let Some(selected) = account_manager.get_selected_account_index() { + i == selected + } else { + false + }; if let Some(op) = add_preview_ui(ui, preview, width, is_selected) { match op { @@ -74,11 +74,17 @@ impl<'a> SimpleProfilePreviewController<'a> { pub fn view_profile_previews( &mut self, - account_manager: &'a AccountManager, + account_manager: &mut AccountManager, ui: &mut egui::Ui, - add_preview_ui: fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, index: usize) -> bool, - ) -> Option { - let mut clicked_at: Option = None; + add_preview_ui: fn( + ui: &mut egui::Ui, + preview: SimpleProfilePreview, + width: f32, + is_selected: bool, + index: usize, + ) -> bool, + ) { + let width = ui.available_width(); for i in 0..account_manager.num_accounts() { if let Some(account) = account_manager.get_account(i) { @@ -90,14 +96,35 @@ impl<'a> SimpleProfilePreviewController<'a> { if let Ok(profile) = profile { let preview = SimpleProfilePreview::new(&profile, self.img_cache); - if add_preview_ui(ui, preview, i) { - clicked_at = Some(i) + let is_selected = + if let Some(selected) = account_manager.get_selected_account_index() { + i == selected + } else { + false + }; + + if add_preview_ui(ui, preview, width, is_selected, i) { + account_manager.select_account(i); } } } } } + } - clicked_at + pub fn show_with_nickname( + &'a self, + ui: &mut egui::Ui, + key: &Pubkey, + ui_element: fn(ui: &mut egui::Ui, username: &DisplayName) -> egui::Response, + ) -> Option { + if let Ok(txn) = Transaction::new(self.ndb) { + let profile = self.ndb.get_profile_by_pubkey(&txn, key.bytes()); + + if let Ok(profile) = profile { + return Some(ui_element(ui, &get_display_name(&profile))); + } + } + None } } From 22264e70f5f5355dd4013907edfc78d5bb6d7870 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Fri, 24 May 2024 18:18:22 -0400 Subject: [PATCH 14/18] Integrate account switcher to side panel Signed-off-by: kernelkind --- src/ui/account_switcher.rs | 12 +++- src/ui/global_popup.rs | 74 ++++++++++++++------ src/ui/profile/profile_preview_controller.rs | 6 +- src/ui/side_panel.rs | 7 +- src/ui/state_in_memory.rs | 6 +- 5 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/ui/account_switcher.rs b/src/ui/account_switcher.rs index 034c22f..352f895 100644 --- a/src/ui/account_switcher.rs +++ b/src/ui/account_switcher.rs @@ -6,7 +6,10 @@ use egui::{ use crate::account_manager::AccountManager; -use super::profile::{preview::SimpleProfilePreview, SimpleProfilePreviewController}; +use super::{ + profile::{preview::SimpleProfilePreview, SimpleProfilePreviewController}, + state_in_memory::{STATE_ACCOUNT_MANAGEMENT, STATE_ACCOUNT_SWITCHER, STATE_SIDE_PANEL}, +}; pub struct AccountSelectionWidget<'a> { account_manager: &'a mut AccountManager, @@ -157,7 +160,12 @@ fn top_section_widget() -> impl egui::Widget { Layout::right_to_left(egui::Align::Center), |ui| { if ui.add(manage_accounts_button()).clicked() { - // TODO: route to AccountLoginView + STATE_ACCOUNT_SWITCHER.set_state(ui.ctx(), false); + STATE_SIDE_PANEL.set_state( + ui.ctx(), + Some(ui::global_popup::GlobalPopupType::AccountManagement), + ); + STATE_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), true); } }, ); diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs index 75edd85..2619187 100644 --- a/src/ui/global_popup.rs +++ b/src/ui/global_popup.rs @@ -3,21 +3,25 @@ use egui::{Align2, CentralPanel, RichText, Vec2, Window}; use crate::Damus; use super::{ - state_in_memory::{STATE_GLOBAL_POPUP, STATE_SIDE_PANEL}, - AccountManagementView, View, + profile::SimpleProfilePreviewController, + state_in_memory::{STATE_ACCOUNT_MANAGEMENT, STATE_ACCOUNT_SWITCHER, STATE_SIDE_PANEL}, + AccountManagementView, AccountSelectionWidget, View, }; #[derive(Clone, Copy, Debug)] pub enum GlobalPopupType { AccountManagement, + AccountSwitcher, } static ACCOUNT_MANAGEMENT_TITLE: &str = "Manage accounts"; +static ACCOUNT_SWITCHER_TITLE: &str = "Account switcher"; impl GlobalPopupType { pub fn title(&self) -> &'static str { match self { Self::AccountManagement => ACCOUNT_MANAGEMENT_TITLE, + Self::AccountSwitcher => ACCOUNT_SWITCHER_TITLE, } } } @@ -46,6 +50,16 @@ fn overlay_window<'a>( .default_size(window_size) } +fn account_switcher_window(open: &'_ mut bool) -> Window<'_> { + egui::Window::new("account switcher") + .title_bar(false) + .collapsible(false) + .anchor(Align2::LEFT_BOTTOM, Vec2::new(0.0, -52.0)) + .fixed_size(Vec2::new(360.0, 406.0)) + .open(open) + .movable(false) +} + static MARGIN: Vec2 = Vec2 { x: 100.0, y: 100.0 }; pub struct DesktopGlobalPopup<'a> { @@ -64,29 +78,49 @@ impl<'a> DesktopGlobalPopup<'a> { } pub fn global_popup(app: &mut Damus, ctx: &egui::Context) { CentralPanel::default().show(ctx, |ui| { - let available_size = ui.available_size(); - let window_size = available_size - MARGIN; - if let Some(popup) = STATE_SIDE_PANEL.get_state(ctx) { - let mut show_global_popup = STATE_GLOBAL_POPUP.get_state(ctx); - if show_global_popup { - overlay_window(&mut show_global_popup, window_size, popup.title()).show( - ctx, - |ui| { - match popup { - GlobalPopupType::AccountManagement => { - AccountManagementView::from_app(app).ui(ui) - } - }; - }, - ); - - // user could have closed the window, set the new state in egui memory - STATE_GLOBAL_POPUP.set_state(ctx, show_global_popup); + match popup { + GlobalPopupType::AccountManagement => { + Self::account_management(app, ctx, ui, popup.title()); + } + GlobalPopupType::AccountSwitcher => { + let mut show_account_switcher = STATE_ACCOUNT_SWITCHER.get_state(ctx); + if show_account_switcher { + STATE_ACCOUNT_MANAGEMENT.set_state(ctx, false); + account_switcher_window(&mut show_account_switcher).show(ctx, |ui| { + AccountSelectionWidget::new( + &mut app.account_manager, + SimpleProfilePreviewController::new( + &app.ndb, + &mut app.img_cache, + ), + ) + .ui(ui); + }); + } + } } } }); } + + fn account_management( + app: &mut Damus, + ctx: &egui::Context, + ui: &mut egui::Ui, + title: &'static str, + ) { + let available_size = ui.available_size(); + let window_size = available_size - MARGIN; + let mut show_account_management = STATE_ACCOUNT_MANAGEMENT.get_state(ctx); + if show_account_management { + overlay_window(&mut show_account_management, window_size, title).show(ctx, |ui| { + AccountManagementView::from_app(app).ui(ui); + }); + // user could have closed the window, set the new state in egui memory + STATE_ACCOUNT_MANAGEMENT.set_state(ctx, show_account_management); + } + } } mod preview { diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs index 15e9e25..1a74884 100644 --- a/src/ui/profile/profile_preview_controller.rs +++ b/src/ui/profile/profile_preview_controller.rs @@ -1,7 +1,10 @@ use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use crate::{account_manager::AccountManager, imgcache::ImageCache, DisplayName}; +use crate::{ + account_manager::AccountManager, imgcache::ImageCache, + ui::state_in_memory::STATE_ACCOUNT_SWITCHER, DisplayName, +}; use super::preview::{get_display_name, SimpleProfilePreview}; @@ -105,6 +108,7 @@ impl<'a> SimpleProfilePreviewController<'a> { if add_preview_ui(ui, preview, width, is_selected, i) { account_manager.select_account(i); + STATE_ACCOUNT_SWITCHER.set_state(ui.ctx(), false); } } } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs index 2c9bb71..b705b44 100644 --- a/src/ui/side_panel.rs +++ b/src/ui/side_panel.rs @@ -3,7 +3,7 @@ use egui::{Button, Layout, SidePanel, Vec2}; use crate::ui::global_popup::GlobalPopupType; use super::{ - state_in_memory::{STATE_GLOBAL_POPUP, STATE_SIDE_PANEL}, + state_in_memory::{STATE_ACCOUNT_SWITCHER, STATE_SIDE_PANEL}, View, }; @@ -32,8 +32,9 @@ impl DesktopSidePanel { .add_sized(Vec2::new(32.0, 32.0), Button::new("A")) .clicked() { - STATE_SIDE_PANEL.set_state(ui.ctx(), Some(GlobalPopupType::AccountManagement)); - STATE_GLOBAL_POPUP.set_state(ui.ctx(), true); + STATE_SIDE_PANEL.set_state(ui.ctx(), Some(GlobalPopupType::AccountSwitcher)); + let previous_val = STATE_ACCOUNT_SWITCHER.get_state(ui.ctx()); + STATE_ACCOUNT_SWITCHER.set_state(ui.ctx(), !previous_val); } ui.add_space(spacing_amt); ui.add(settings_button(dark_mode)); diff --git a/src/ui/state_in_memory.rs b/src/ui/state_in_memory.rs index 7823cb8..09d37f1 100644 --- a/src/ui/state_in_memory.rs +++ b/src/ui/state_in_memory.rs @@ -33,11 +33,11 @@ pub static STATE_SIDE_PANEL: StateInMemory> = default_state: None, }; -pub static STATE_GLOBAL_POPUP: StateInMemory = StateInMemory:: { - id: GLOBAL_POPUP_VIEW_STATE_ID, +pub static STATE_ACCOUNT_SWITCHER: StateInMemory = StateInMemory:: { + id: ACCOUNT_SWITCHER_VIEW_STATE_ID, default_state: false, }; static ACCOUNT_MANAGEMENT_VIEW_STATE_ID: &str = "account management view state"; static SIDE_PANEL_VIEW_STATE_ID: &str = "side panel view state"; -static GLOBAL_POPUP_VIEW_STATE_ID: &str = "global popup view state"; +static ACCOUNT_SWITCHER_VIEW_STATE_ID: &str = "account switcher view state"; From 7ebd694f11d8e7603192c6f8e924f1321ddc20ec Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sat, 25 May 2024 17:00:21 -0400 Subject: [PATCH 15/18] refactor account switcher & management previews Signed-off-by: kernelkind --- src/test_data.rs | 32 ++++++++- src/ui/account_management.rs | 70 +------------------- src/ui/account_switcher.rs | 49 ++++++++++++++ src/ui/profile/profile_preview_controller.rs | 8 +++ 4 files changed, 89 insertions(+), 70 deletions(-) diff --git a/src/test_data.rs b/src/test_data.rs index 5797e7e..f99bd59 100644 --- a/src/test_data.rs +++ b/src/test_data.rs @@ -1,7 +1,14 @@ -use enostr::{FullKeypair, Pubkey, RelayPool}; -use nostrdb::ProfileRecord; +use std::path::Path; -use crate::account_manager::UserAccount; +use enostr::{FullKeypair, Pubkey, RelayPool}; +use nostrdb::{Config, Ndb, ProfileRecord}; + +use crate::{ + account_manager::{AccountManager, UserAccount}, + imgcache::ImageCache, + key_storage::KeyStorage, + relay_generation::RelayGenerator, +}; #[allow(unused_must_use)] pub fn sample_pool() -> RelayPool { @@ -86,3 +93,22 @@ pub fn get_test_accounts() -> Vec { }) .collect() } + +pub fn get_accmgr_and_ndb_and_imgcache() -> (AccountManager, Ndb, ImageCache) { + let mut account_manager = + AccountManager::new(None, KeyStorage::None, RelayGenerator::Constant, || {}); + let accounts = get_test_accounts(); + accounts + .into_iter() + .for_each(|acc| account_manager.add_account(acc.key, || {})); + + let mut config = Config::new(); + config.set_ingester_threads(2); + + let db_dir = Path::new("."); + let path = db_dir.to_str().unwrap(); + let ndb = Ndb::new(path, &config).expect("ndb"); + let imgcache_dir = db_dir.join("cache/img"); + let img_cache = ImageCache::new(imgcache_dir); + (account_manager, ndb, img_cache) +} diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 06b3a27..68918f2 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -244,15 +244,10 @@ fn selected_widget() -> impl egui::Widget { // PREVIEWS mod preview { - use nostrdb::{Config, Ndb}; - use ui::account_switcher::AccountSelectionWidget; + use nostrdb::Ndb; use super::*; - use crate::imgcache::ImageCache; - use crate::key_storage::KeyStorage; - use crate::relay_generation::RelayGenerator; - use crate::test_data; - use std::path::Path; + use crate::{imgcache::ImageCache, test_data::get_accmgr_and_ndb_and_imgcache}; pub struct AccountManagementPreview { account_manager: AccountManager, @@ -260,27 +255,9 @@ mod preview { img_cache: ImageCache, } - fn get_ndb_and_img_cache() -> (Ndb, ImageCache) { - let mut config = Config::new(); - config.set_ingester_threads(2); - - let db_dir = Path::new("."); - let path = db_dir.to_str().unwrap(); - let ndb = Ndb::new(path, &config).expect("ndb"); - let imgcache_dir = db_dir.join("cache/img"); - let img_cache = ImageCache::new(imgcache_dir); - (ndb, img_cache) - } - impl AccountManagementPreview { fn new() -> Self { - let mut account_manager = - AccountManager::new(None, KeyStorage::None, RelayGenerator::Constant, || {}); - let accounts = test_data::get_test_accounts(); - accounts - .into_iter() - .for_each(|acc| account_manager.add_account(acc.key, || {})); - let (ndb, img_cache) = get_ndb_and_img_cache(); + let (account_manager, ndb, img_cache) = get_accmgr_and_ndb_and_imgcache(); AccountManagementPreview { account_manager, @@ -308,45 +285,4 @@ mod preview { AccountManagementPreview::new() } } - - pub struct AccountSelectionPreview { - account_manager: AccountManager, - ndb: Ndb, - img_cache: ImageCache, - } - - impl AccountSelectionPreview { - fn new() -> Self { - let mut account_manager = - AccountManager::new(None, KeyStorage::None, RelayGenerator::Constant, || {}); - let accounts = test_data::get_test_accounts(); - accounts - .into_iter() - .for_each(|acc| account_manager.add_account(acc.key, || {})); - let (ndb, img_cache) = get_ndb_and_img_cache(); - AccountSelectionPreview { - account_manager, - ndb, - img_cache, - } - } - } - - impl View for AccountSelectionPreview { - fn ui(&mut self, ui: &mut egui::Ui) { - AccountSelectionWidget::new( - &mut self.account_manager, - SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), - ) - .ui(ui); - } - } - - impl<'a> Preview for AccountSelectionWidget<'a> { - type Prev = AccountSelectionPreview; - - fn preview() -> Self::Prev { - AccountSelectionPreview::new() - } - } } diff --git a/src/ui/account_switcher.rs b/src/ui/account_switcher.rs index 352f895..e786f2a 100644 --- a/src/ui/account_switcher.rs +++ b/src/ui/account_switcher.rs @@ -66,6 +66,7 @@ impl<'a> AccountSelectionWidget<'a> { account_switcher_card_ui(), ); } + fn sign_out_button(&self, ui: &mut egui::Ui, account: &UserAccount) -> Option { self.simple_preview_controller.show_with_nickname( ui, @@ -193,3 +194,51 @@ fn add_account_button() -> egui::Button<'static> { let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); Button::image_and_text(img, RichText::new(" Add account").size(16.0).color(PINK)).frame(false) } + +mod previews { + use nostrdb::Ndb; + + use crate::{ + account_manager::AccountManager, + imgcache::ImageCache, + test_data, + ui::{profile::SimpleProfilePreviewController, Preview, View}, + }; + + use super::AccountSelectionWidget; + + pub struct AccountSelectionPreview { + account_manager: AccountManager, + ndb: Ndb, + img_cache: ImageCache, + } + + impl AccountSelectionPreview { + fn new() -> Self { + let (account_manager, ndb, img_cache) = test_data::get_accmgr_and_ndb_and_imgcache(); + AccountSelectionPreview { + account_manager, + ndb, + img_cache, + } + } + } + + impl View for AccountSelectionPreview { + fn ui(&mut self, ui: &mut egui::Ui) { + AccountSelectionWidget::new( + &mut self.account_manager, + SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), + ) + .ui(ui); + } + } + + impl<'a> Preview for AccountSelectionWidget<'a> { + type Prev = AccountSelectionPreview; + + fn preview() -> Self::Prev { + AccountSelectionPreview::new() + } + } +} diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs index 1a74884..5786d31 100644 --- a/src/ui/profile/profile_preview_controller.rs +++ b/src/ui/profile/profile_preview_controller.rs @@ -131,4 +131,12 @@ impl<'a> SimpleProfilePreviewController<'a> { } None } + + pub fn show_with_pfp( + &'a self, + ui: &mut egui::Ui, + key: &Pubkey, + ui_element: fn(ui: &mut egui::Ui, preview: SimpleProfilePreview) -> egui::Response, + ) { + } } From df0377cb891c44797df29ec950c2e019d5fff178 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 26 May 2024 22:05:45 -0400 Subject: [PATCH 16/18] Pfp integration to side panel Signed-off-by: kernelkind --- src/app.rs | 15 +++- src/ui/global_popup.rs | 7 +- src/ui/profile/preview.rs | 2 +- src/ui/profile/profile_preview_controller.rs | 24 ++++-- src/ui/side_panel.rs | 91 +++++++++++++++----- 5 files changed, 109 insertions(+), 30 deletions(-) diff --git a/src/app.rs b/src/app.rs index 52993ee..891d75d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ use crate::notecache::{CachedNote, NoteCache}; use crate::relay_pool_manager::create_wakeup; use crate::timeline; use crate::timeline::{NoteRef, Timeline, ViewFilter}; +use crate::ui::profile::SimpleProfilePreviewController; use crate::ui::{is_mobile, DesktopGlobalPopup, DesktopSidePanel, View}; use crate::Result; @@ -853,7 +854,11 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) { if app.timelines.len() == 1 { DesktopSidePanel::panel().show(ctx, |ui| { - DesktopSidePanel::inner(ui); + DesktopSidePanel::new( + &mut app.account_manager, + SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), + ) + .inner(ui); }); main_panel(&ctx.style()).show(ctx, |ui| { DesktopGlobalPopup::new(app).ui(ui); @@ -882,7 +887,13 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: us .sizes(sizes, timelines) .clip(true) .horizontal(|mut strip| { - strip.cell(crate::ui::DesktopSidePanel::inner); + strip.cell(|ui| { + DesktopSidePanel::new( + &mut app.account_manager, + SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), + ) + .inner(ui) + }); for timeline_ind in 0..timelines { strip.cell(|ui| timeline::timeline_view(ui, app, timeline_ind)); diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs index 2619187..f1bd505 100644 --- a/src/ui/global_popup.rs +++ b/src/ui/global_popup.rs @@ -126,7 +126,7 @@ impl<'a> DesktopGlobalPopup<'a> { mod preview { use crate::{ test_data, - ui::{DesktopSidePanel, Preview, View}, + ui::{profile::SimpleProfilePreviewController, DesktopSidePanel, Preview, View}, Damus, }; @@ -157,7 +157,10 @@ mod preview { impl View for GlobalPopupPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let mut panel = DesktopSidePanel::new(); + let mut panel = DesktopSidePanel::new( + &mut self.app.account_manager, + SimpleProfilePreviewController::new(&self.app.ndb, &mut self.app.img_cache), + ); DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); DesktopGlobalPopup::new(&mut self.app).ui(ui); } diff --git a/src/ui/profile/preview.rs b/src/ui/profile/preview.rs index 3d851ae..806c39c 100644 --- a/src/ui/profile/preview.rs +++ b/src/ui/profile/preview.rs @@ -156,7 +156,7 @@ pub fn get_display_name<'a>(profile: &'a ProfileRecord<'a>) -> DisplayName<'a> { } } -fn get_profile_url<'a>(profile: &'a ProfileRecord<'a>) -> &'a str { +pub fn get_profile_url<'a>(profile: &'a ProfileRecord<'a>) -> &'a str { if let Some(url) = profile.record().profile().and_then(|p| p.picture()) { url } else { diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs index 5786d31..f154bad 100644 --- a/src/ui/profile/profile_preview_controller.rs +++ b/src/ui/profile/profile_preview_controller.rs @@ -6,7 +6,10 @@ use crate::{ ui::state_in_memory::STATE_ACCOUNT_SWITCHER, DisplayName, }; -use super::preview::{get_display_name, SimpleProfilePreview}; +use super::{ + preview::{get_display_name, get_profile_url, SimpleProfilePreview}, + ProfilePic, +}; pub struct SimpleProfilePreviewController<'a> { ndb: &'a Ndb, @@ -117,7 +120,7 @@ impl<'a> SimpleProfilePreviewController<'a> { } pub fn show_with_nickname( - &'a self, + &self, ui: &mut egui::Ui, key: &Pubkey, ui_element: fn(ui: &mut egui::Ui, username: &DisplayName) -> egui::Response, @@ -133,10 +136,21 @@ impl<'a> SimpleProfilePreviewController<'a> { } pub fn show_with_pfp( - &'a self, + &mut self, ui: &mut egui::Ui, key: &Pubkey, - ui_element: fn(ui: &mut egui::Ui, preview: SimpleProfilePreview) -> egui::Response, - ) { + ui_element: fn(ui: &mut egui::Ui, pfp: ProfilePic) -> egui::Response, + ) -> Option { + if let Ok(txn) = Transaction::new(self.ndb) { + let profile = self.ndb.get_profile_by_pubkey(&txn, key.bytes()); + + if let Ok(profile) = profile { + return Some(ui_element( + ui, + ProfilePic::new(self.img_cache, get_profile_url(&profile)), + )); + } + } + None } } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs index b705b44..a81b808 100644 --- a/src/ui/side_panel.rs +++ b/src/ui/side_panel.rs @@ -1,37 +1,43 @@ -use egui::{Button, Layout, SidePanel, Vec2}; +use egui::{Button, Layout, SidePanel, Vec2, Widget}; -use crate::ui::global_popup::GlobalPopupType; +use crate::{account_manager::AccountManager, ui::global_popup::GlobalPopupType}; use super::{ + profile::SimpleProfilePreviewController, state_in_memory::{STATE_ACCOUNT_SWITCHER, STATE_SIDE_PANEL}, - View, + ProfilePic, View, }; -#[derive(Default)] -pub struct DesktopSidePanel {} +pub struct DesktopSidePanel<'a> { + account_manager: &'a mut AccountManager, + simple_preview_controller: SimpleProfilePreviewController<'a>, +} static ID: &str = "left panel"; -impl View for DesktopSidePanel { +impl<'a> View for DesktopSidePanel<'a> { fn ui(&mut self, ui: &mut egui::Ui) { - DesktopSidePanel::inner(ui); + self.inner(ui); } } -impl DesktopSidePanel { - pub fn new() -> Self { - DesktopSidePanel::default() +impl<'a> DesktopSidePanel<'a> { + pub fn new( + account_manager: &'a mut AccountManager, + simple_preview_controller: SimpleProfilePreviewController<'a>, + ) -> Self { + DesktopSidePanel { + account_manager, + simple_preview_controller, + } } - pub fn inner(ui: &mut egui::Ui) { + pub fn inner(&mut self, ui: &mut egui::Ui) { let dark_mode = ui.ctx().style().visuals.dark_mode; let spacing_amt = 16.0; ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| { ui.add_space(spacing_amt); - if ui - .add_sized(Vec2::new(32.0, 32.0), Button::new("A")) - .clicked() - { + if self.pfp_button(ui).clicked() { STATE_SIDE_PANEL.set_state(ui.ctx(), Some(GlobalPopupType::AccountSwitcher)); let previous_val = STATE_ACCOUNT_SWITCHER.get_state(ui.ctx()); STATE_ACCOUNT_SWITCHER.set_state(ui.ctx(), !previous_val); @@ -44,11 +50,32 @@ impl DesktopSidePanel { }); } + fn pfp_button(&mut self, ui: &mut egui::Ui) -> egui::Response { + if let Some(selected_account) = self.account_manager.get_selected_account() { + if let Some(response) = self.simple_preview_controller.show_with_pfp( + ui, + &selected_account.key.pubkey, + show_pfp(), + ) { + return response; + } + } + + add_button_to_ui(ui, no_account_pfp()) + } + pub fn panel() -> SidePanel { egui::SidePanel::left(ID).resizable(false).exact_width(40.0) } } +fn show_pfp() -> fn(ui: &mut egui::Ui, pfp: ProfilePic) -> egui::Response { + |ui, pfp| { + let response = pfp.ui(ui); + ui.allocate_rect(response.rect, egui::Sense::click()) + } +} + fn settings_button(dark_mode: bool) -> egui::Button<'static> { let _ = dark_mode; let img_data = egui::include_image!("../../assets/icons/settings_dark_4x.png"); @@ -56,6 +83,16 @@ fn settings_button(dark_mode: bool) -> egui::Button<'static> { egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false) } +fn add_button_to_ui(ui: &mut egui::Ui, button: Button) -> egui::Response { + ui.add_sized(Vec2::new(32.0, 32.0), button) +} + +fn no_account_pfp() -> Button<'static> { + Button::new("A") + .rounding(20.0) + .min_size(Vec2::new(38.0, 38.0)) +} + fn add_column_button(dark_mode: bool) -> egui::Button<'static> { let _ = dark_mode; let img_data = egui::include_image!("../../assets/icons/add_column_dark_4x.png"); @@ -64,26 +101,40 @@ fn add_column_button(dark_mode: bool) -> egui::Button<'static> { } mod preview { - use crate::ui::Preview; + use nostrdb::Ndb; + + use crate::{imgcache::ImageCache, test_data, ui::Preview}; use super::*; - pub struct DesktopSidePanelPreview {} + pub struct DesktopSidePanelPreview { + account_manager: AccountManager, + ndb: Ndb, + img_cache: ImageCache, + } impl DesktopSidePanelPreview { fn new() -> Self { - DesktopSidePanelPreview {} + let (account_manager, ndb, img_cache) = test_data::get_accmgr_and_ndb_and_imgcache(); + DesktopSidePanelPreview { + account_manager, + ndb, + img_cache, + } } } impl View for DesktopSidePanelPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let mut panel = DesktopSidePanel::new(); + let mut panel = DesktopSidePanel::new( + &mut self.account_manager, + SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), + ); DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); } } - impl Preview for DesktopSidePanel { + impl<'a> Preview for DesktopSidePanel<'a> { type Prev = DesktopSidePanelPreview; fn preview() -> Self::Prev { From 31b2b5c950591536263f3218bf4a11e8f6b59ec1 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Mon, 27 May 2024 12:22:40 -0700 Subject: [PATCH 17/18] initial refactor in preparation for routing Signed-off-by: William Casarin --- enostr/src/keypair.rs | 6 +- src/account_manager.rs | 49 ++---- src/app.rs | 47 ++---- src/key_parsing.rs | 6 +- src/key_storage.rs | 10 +- src/lib.rs | 1 + src/macos_key_storage.rs | 28 ++-- src/relay_generation.rs | 42 +---- src/route.rs | 7 + src/test_data.rs | 22 +-- src/ui/account_management.rs | 90 +++++----- src/ui/account_switcher.rs | 144 +++++++++------- src/ui/global_popup.rs | 168 ------------------- src/ui/mod.rs | 3 - src/ui/profile/profile_preview_controller.rs | 140 +++++++++------- src/ui/side_panel.rs | 95 ++++++----- src/ui/state_in_memory.rs | 43 ----- src/ui_preview/main.rs | 5 +- src/user_account.rs | 13 +- 19 files changed, 332 insertions(+), 587 deletions(-) create mode 100644 src/route.rs delete mode 100644 src/ui/global_popup.rs delete mode 100644 src/ui/state_in_memory.rs diff --git a/enostr/src/keypair.rs b/enostr/src/keypair.rs index 6d0c4a2..44929e8 100644 --- a/enostr/src/keypair.rs +++ b/enostr/src/keypair.rs @@ -8,7 +8,7 @@ pub struct Keypair { } impl Keypair { - pub fn new(secret_key: SecretKey) -> Self { + pub fn from_secret(secret_key: SecretKey) -> Self { let cloned_secret_key = secret_key.clone(); let nostr_keys = nostr::Keys::new(secret_key); Keypair { @@ -17,6 +17,10 @@ impl Keypair { } } + pub fn new(pubkey: Pubkey, secret_key: Option) -> Self { + Keypair { pubkey, secret_key } + } + pub fn only_pubkey(pubkey: Pubkey) -> Self { Keypair { pubkey, diff --git a/src/account_manager.rs b/src/account_manager.rs index e610394..91077c1 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -1,9 +1,9 @@ use std::cmp::Ordering; -use enostr::FullKeypair; +use enostr::Keypair; +use crate::key_storage::KeyStorage; pub use crate::user_account::UserAccount; -use crate::{key_storage::KeyStorage, relay_generation::RelayGenerator}; /// The interface for managing the user's accounts. /// Represents all user-facing operations related to account management. @@ -11,34 +11,16 @@ pub struct AccountManager { currently_selected_account: Option, accounts: Vec, key_store: KeyStorage, - relay_generator: RelayGenerator, } impl AccountManager { - pub fn new( - currently_selected_account: Option, - key_store: KeyStorage, - // TODO: right now, there is only one way of generating relays for all accounts. In the future - // each account should have the option of generating relays differently - relay_generator: RelayGenerator, - wakeup: impl Fn() + Send + Sync + Clone + 'static, - ) -> Self { - let accounts = if let Ok(keys) = key_store.get_keys() { - keys.into_iter() - .map(|key| { - let relays = relay_generator.generate_relays_for(&key.pubkey, wakeup.clone()); - UserAccount { key, relays } - }) - .collect() - } else { - Vec::new() - }; + pub fn new(currently_selected_account: Option, key_store: KeyStorage) -> Self { + let accounts = key_store.get_keys().unwrap_or_default(); AccountManager { currently_selected_account, accounts, key_store, - relay_generator, } } @@ -46,13 +28,17 @@ impl AccountManager { &self.accounts } - pub fn get_account(&self, index: usize) -> Option<&UserAccount> { - self.accounts.get(index) + pub fn get_account(&self, ind: usize) -> Option<&UserAccount> { + self.accounts.get(ind) + } + + pub fn find_account(&self, pk: &[u8; 32]) -> Option<&UserAccount> { + self.accounts.iter().find(|acc| acc.pubkey.bytes() == pk) } pub fn remove_account(&mut self, index: usize) { if let Some(account) = self.accounts.get(index) { - let _ = self.key_store.remove_key(&account.key); + let _ = self.key_store.remove_key(account); self.accounts.remove(index); if let Some(selected_index) = self.currently_selected_account { @@ -69,17 +55,8 @@ impl AccountManager { } } - pub fn add_account( - &mut self, - key: FullKeypair, - wakeup: impl Fn() + Send + Sync + Clone + 'static, - ) { - let _ = self.key_store.add_key(&key); - let relays = self - .relay_generator - .generate_relays_for(&key.pubkey, wakeup); - let account = UserAccount { key, relays }; - + pub fn add_account(&mut self, account: Keypair) { + let _ = self.key_store.add_key(&account); self.accounts.push(account) } diff --git a/src/app.rs b/src/app.rs index 891d75d..2d48a83 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,11 +5,11 @@ use crate::error::Error; use crate::frame_history::FrameHistory; use crate::imgcache::ImageCache; use crate::notecache::{CachedNote, NoteCache}; -use crate::relay_pool_manager::create_wakeup; +use crate::route::Route; use crate::timeline; use crate::timeline::{NoteRef, Timeline, ViewFilter}; use crate::ui::profile::SimpleProfilePreviewController; -use crate::ui::{is_mobile, DesktopGlobalPopup, DesktopSidePanel, View}; +use crate::ui::{is_mobile, DesktopSidePanel}; use crate::Result; use egui::{Context, Frame, Style}; @@ -39,6 +39,8 @@ pub struct Damus { note_cache: NoteCache, pool: RelayPool, + /// global navigation for account management popups, etc. + nav: Vec, pub textmode: bool, pub timelines: Vec, @@ -645,6 +647,7 @@ impl Damus { img_cache: ImageCache::new(imgcache_dir), note_cache: NoteCache::default(), selected_timeline: 0, + nav: Vec::with_capacity(6), timelines, textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), @@ -653,9 +656,6 @@ impl Damus { None, // TODO: use correct KeyStorage mechanism for current OS arch crate::key_storage::KeyStorage::None, - // TODO: setting for relay generator - crate::relay_generation::RelayGenerator::Constant, - create_wakeup(&cc.egui_ctx), ), //compose: "".to_string(), frame_history: FrameHistory::default(), @@ -680,14 +680,10 @@ impl Damus { note_cache: NoteCache::default(), selected_timeline: 0, timelines, + nav: vec![], textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), - account_manager: AccountManager::new( - None, - crate::key_storage::KeyStorage::None, - crate::relay_generation::RelayGenerator::Constant, - || {}, - ), + account_manager: AccountManager::new(None, crate::key_storage::KeyStorage::None), frame_history: FrameHistory::default(), } } @@ -852,25 +848,8 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) { Size::remainder() }; - if app.timelines.len() == 1 { - DesktopSidePanel::panel().show(ctx, |ui| { - DesktopSidePanel::new( - &mut app.account_manager, - SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), - ) - .inner(ui); - }); - main_panel(&ctx.style()).show(ctx, |ui| { - DesktopGlobalPopup::new(app).ui(ui); - timeline::timeline_view(ui, app, 0); - }); - - return; - } - main_panel(&ctx.style()).show(ctx, |ui| { ui.spacing_mut().item_spacing.x = 0.0; - DesktopGlobalPopup::new(app).ui(ui); if need_scroll { egui::ScrollArea::horizontal().show(ui, |ui| { timelines_view(ui, panel_sizes, app, app.timelines.len()); @@ -888,11 +867,17 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: us .clip(true) .horizontal(|mut strip| { strip.cell(|ui| { - DesktopSidePanel::new( - &mut app.account_manager, + if DesktopSidePanel::new( + app.account_manager + .get_selected_account() + .map(|a| a.pubkey.bytes()), SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), ) - .inner(ui) + .show(ui) + .clicked() + { + // clicked pfp + } }); for timeline_ind in 0..timelines { diff --git a/src/key_parsing.rs b/src/key_parsing.rs index 59d7842..a6968fa 100644 --- a/src/key_parsing.rs +++ b/src/key_parsing.rs @@ -125,7 +125,7 @@ pub async fn get_login_key(key: &str) -> Result { } else if let Ok(pubkey) = Pubkey::try_from_hex_str_with_verify(tmp_key) { Ok(Keypair::only_pubkey(pubkey)) } else if let Ok(secret_key) = SecretKey::from_str(tmp_key) { - Ok(Keypair::new(secret_key)) + Ok(Keypair::from_secret(secret_key)) } else { Err(LoginError::InvalidKey) } @@ -181,7 +181,7 @@ mod tests { promise_assert!( assert_eq, - Ok(Keypair::new(expected_privkey)), + Ok(Keypair::from_secret(expected_privkey)), &login_key_result ); } @@ -194,7 +194,7 @@ mod tests { promise_assert!( assert_eq, - Ok(Keypair::new(expected_privkey)), + Ok(Keypair::from_secret(expected_privkey)), &login_key_result ); } diff --git a/src/key_storage.rs b/src/key_storage.rs index 06d1814..0503dba 100644 --- a/src/key_storage.rs +++ b/src/key_storage.rs @@ -1,4 +1,4 @@ -use enostr::FullKeypair; +use enostr::Keypair; #[cfg(target_os = "macos")] use crate::macos_key_storage::MacOSKeyStorage; @@ -17,15 +17,15 @@ pub enum KeyStorage { } impl KeyStorage { - pub fn get_keys(&self) -> Result, KeyStorageError> { + pub fn get_keys(&self) -> Result, KeyStorageError> { match self { Self::None => Ok(Vec::new()), #[cfg(target_os = "macos")] - Self::MacOS => Ok(MacOSKeyStorage::new(SERVICE_NAME).get_all_fullkeypairs()), + Self::MacOS => Ok(MacOSKeyStorage::new(SERVICE_NAME).get_all_keypairs()), } } - pub fn add_key(&self, key: &FullKeypair) -> Result<(), KeyStorageError> { + pub fn add_key(&self, key: &Keypair) -> Result<(), KeyStorageError> { let _ = key; match self { Self::None => Ok(()), @@ -34,7 +34,7 @@ impl KeyStorage { } } - pub fn remove_key(&self, key: &FullKeypair) -> Result<(), KeyStorageError> { + pub fn remove_key(&self, key: &Keypair) -> Result<(), KeyStorageError> { let _ = key; match self { Self::None => Ok(()), diff --git a/src/lib.rs b/src/lib.rs index bb33cb6..f10c945 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ mod profile; mod relay_generation; pub mod relay_pool_manager; mod result; +mod route; mod test_data; mod time; mod timecache; diff --git a/src/macos_key_storage.rs b/src/macos_key_storage.rs index e47a1c2..5c96017 100644 --- a/src/macos_key_storage.rs +++ b/src/macos_key_storage.rs @@ -1,6 +1,6 @@ #![cfg(target_os = "macos")] -use enostr::{FullKeypair, Pubkey, SecretKey}; +use enostr::{Keypair, Pubkey, SecretKey}; use security_framework::item::{ItemClass, ItemSearchOptions, Limit, SearchResult}; use security_framework::passwords::{delete_generic_password, set_generic_password}; @@ -16,11 +16,13 @@ impl<'a> MacOSKeyStorage<'a> { MacOSKeyStorage { service_name } } - pub fn add_key(&self, key: &FullKeypair) -> Result<(), KeyStorageError> { + pub fn add_key(&self, key: &Keypair) -> Result<(), KeyStorageError> { match set_generic_password( self.service_name, key.pubkey.hex().as_str(), - key.secret_key.as_secret_bytes(), + key.secret_key + .as_ref() + .map_or_else(|| &[] as &[u8], |sc| sc.as_secret_bytes()), ) { Ok(_) => Ok(()), Err(_) => Err(KeyStorageError::Addition(key.pubkey.hex())), @@ -82,12 +84,12 @@ impl<'a> MacOSKeyStorage<'a> { } } - pub fn get_all_fullkeypairs(&self) -> Vec { + pub fn get_all_keypairs(&self) -> Vec { self.get_pubkeys() .iter() - .filter_map(|pubkey| { + .map(|pubkey| { let maybe_secret = self.get_secret_key_for_pubkey(pubkey); - maybe_secret.map(|secret| FullKeypair::new(pubkey.clone(), secret)) + Keypair::new(pubkey.clone(), maybe_secret) }) .collect() } @@ -106,6 +108,8 @@ impl<'a> MacOSKeyStorage<'a> { #[cfg(test)] mod tests { use super::*; + use enostr::FullKeypair; + static TEST_SERVICE_NAME: &str = "NOTEDECKTEST"; static STORAGE: MacOSKeyStorage = MacOSKeyStorage { service_name: TEST_SERVICE_NAME, @@ -119,7 +123,7 @@ mod tests { fn add_and_remove_test_pubkey_only() { let num_keys_before_test = STORAGE.get_pubkeys().len(); - let keypair = FullKeypair::generate(); + let keypair = FullKeypair::generate().to_keypair(); let add_result = STORAGE.add_key(&keypair); assert_eq!(add_result, Ok(())); @@ -134,18 +138,20 @@ mod tests { } fn add_and_remove_full_n(n: usize) { - let num_keys_before_test = STORAGE.get_all_fullkeypairs().len(); + let num_keys_before_test = STORAGE.get_all_keypairs().len(); // there must be zero keys in storage for the test to work as intended assert_eq!(num_keys_before_test, 0); - let expected_keypairs: Vec = (0..n).map(|_| FullKeypair::generate()).collect(); + let expected_keypairs: Vec = (0..n) + .map(|_| FullKeypair::generate().to_keypair()) + .collect(); expected_keypairs.iter().for_each(|keypair| { let add_result = STORAGE.add_key(keypair); assert_eq!(add_result, Ok(())); }); - let asserted_keypairs = STORAGE.get_all_fullkeypairs(); + let asserted_keypairs = STORAGE.get_all_keypairs(); assert_eq!(expected_keypairs, asserted_keypairs); expected_keypairs.iter().for_each(|keypair| { @@ -153,7 +159,7 @@ mod tests { assert_eq!(remove_result, Ok(())); }); - let num_keys_after_test = STORAGE.get_all_fullkeypairs().len(); + let num_keys_after_test = STORAGE.get_all_keypairs().len(); assert_eq!(num_keys_after_test, 0); } diff --git a/src/relay_generation.rs b/src/relay_generation.rs index 8bd33a6..7308abe 100644 --- a/src/relay_generation.rs +++ b/src/relay_generation.rs @@ -1,45 +1,7 @@ -use enostr::{Pubkey, RelayPool}; +use enostr::RelayPool; use tracing::error; -pub enum RelayGenerator { - GossipModel, - Nip65, - Constant, -} - -impl RelayGenerator { - pub fn generate_relays_for( - &self, - key: &Pubkey, - wakeup: impl Fn() + Send + Sync + Clone + 'static, - ) -> RelayPool { - match self { - Self::GossipModel => generate_relays_gossip(key, wakeup), - Self::Nip65 => generate_relays_nip65(key, wakeup), - Self::Constant => generate_constant_relays(wakeup), - } - } -} - -fn generate_relays_gossip( - key: &Pubkey, - wakeup: impl Fn() + Send + Sync + Clone + 'static, -) -> RelayPool { - let _ = wakeup; - let _ = key; - todo!() -} - -fn generate_relays_nip65( - key: &Pubkey, - wakeup: impl Fn() + Send + Sync + Clone + 'static, -) -> RelayPool { - let _ = wakeup; - let _ = key; - todo!() -} - -fn generate_constant_relays(wakeup: impl Fn() + Send + Sync + Clone + 'static) -> RelayPool { +fn test_relay_pool(wakeup: impl Fn() + Send + Sync + Clone + 'static) -> RelayPool { let mut pool = RelayPool::new(); if let Err(e) = pool.add_url("ws://localhost:8080".to_string(), wakeup.clone()) { diff --git a/src/route.rs b/src/route.rs new file mode 100644 index 0000000..35006f6 --- /dev/null +++ b/src/route.rs @@ -0,0 +1,7 @@ +use nostrdb::NoteKey; + +/// App routing. These describe different places you can go inside Notedeck. +pub enum Route { + ManageAccount, + Thread(NoteKey), +} diff --git a/src/test_data.rs b/src/test_data.rs index f99bd59..2cb19ab 100644 --- a/src/test_data.rs +++ b/src/test_data.rs @@ -7,7 +7,6 @@ use crate::{ account_manager::{AccountManager, UserAccount}, imgcache::ImageCache, key_storage::KeyStorage, - relay_generation::RelayGenerator, }; #[allow(unused_must_use)] @@ -81,26 +80,19 @@ pub fn get_test_accounts() -> Vec { TEN_ACCOUNT_HEXES .iter() .map(|account_hex| { - let key = FullKeypair::new( - Pubkey::from_hex(account_hex).unwrap(), - FullKeypair::generate().secret_key, - ); - - UserAccount { - key, - relays: sample_pool(), - } + let mut kp = FullKeypair::generate().to_keypair(); + kp.pubkey = Pubkey::from_hex(account_hex).unwrap(); + kp }) .collect() } pub fn get_accmgr_and_ndb_and_imgcache() -> (AccountManager, Ndb, ImageCache) { - let mut account_manager = - AccountManager::new(None, KeyStorage::None, RelayGenerator::Constant, || {}); + let mut account_manager = AccountManager::new(None, KeyStorage::None); let accounts = get_test_accounts(); - accounts - .into_iter() - .for_each(|acc| account_manager.add_account(acc.key, || {})); + for account in accounts { + account_manager.add_account(account); + } let mut config = Config::new(); config.set_ingester_threads(2); diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 68918f2..2fe22d7 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -1,5 +1,4 @@ use crate::colors::PINK; -use crate::ui::global_popup::FromApp; use crate::{ account_manager::AccountManager, app_style::NotedeckTextStyle, @@ -7,7 +6,6 @@ use crate::{ }; use egui::{Align, Button, Frame, Image, Layout, RichText, ScrollArea, Vec2}; -use super::global_popup::GlobalPopupType; use super::profile::preview::SimpleProfilePreview; use super::profile::{ProfilePreviewOp, SimpleProfilePreviewController}; @@ -39,7 +37,7 @@ impl<'a> AccountManagementView<'a> { fn show(&mut self, ui: &mut egui::Ui) { Frame::none().outer_margin(24.0).show(ui, |ui| { - ui.add(self.top_section_buttons_widget()); + self.top_section_buttons_widget(ui); ui.add_space(8.0); scroll_area().show(ui, |ui| { self.show_accounts(ui); @@ -86,8 +84,9 @@ impl<'a> AccountManagementView<'a> { fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response { egui::CentralPanel::default() .show(ui.ctx(), |ui| { - ui.add(mobile_title()); - ui.add(self.top_section_buttons_widget()); + mobile_title(ui); + self.top_section_buttons_widget(ui); + ui.add_space(8.0); scroll_area().show(ui, |ui| { self.show_accounts_mobile(ui); @@ -96,34 +95,32 @@ impl<'a> AccountManagementView<'a> { .response } - fn top_section_buttons_widget(&mut self) -> impl egui::Widget + '_ { - |ui: &mut egui::Ui| { - ui.horizontal(|ui| { - ui.allocate_ui_with_layout( - Vec2::new(ui.available_size_before_wrap().x, 32.0), - Layout::left_to_right(egui::Align::Center), - |ui| { - if ui.add(add_account_button()).clicked() { - // TODO: route to AccountLoginView - } - }, - ); + fn top_section_buttons_widget(&mut self, ui: &mut egui::Ui) -> egui::Response { + ui.horizontal(|ui| { + ui.allocate_ui_with_layout( + Vec2::new(ui.available_size_before_wrap().x, 32.0), + Layout::left_to_right(egui::Align::Center), + |ui| { + if ui.add(add_account_button()).clicked() { + // TODO: route to AccountLoginView + } + }, + ); - // UNCOMMENT FOR LOGOUTALL BUTTON - // ui.allocate_ui_with_layout( - // Vec2::new(ui.available_size_before_wrap().x, 32.0), - // Layout::right_to_left(egui::Align::Center), - // |ui| { - // if ui.add(logout_all_button()).clicked() { - // for index in (0..self.account_manager.num_accounts()).rev() { - // self.account_manager.remove_account(index); - // } - // } - // }, - // ); - }) - .response - } + // UNCOMMENT FOR LOGOUTALL BUTTON + // ui.allocate_ui_with_layout( + // Vec2::new(ui.available_size_before_wrap().x, 32.0), + // Layout::right_to_left(egui::Align::Center), + // |ui| { + // if ui.add(logout_all_button()).clicked() { + // for index in (0..self.account_manager.num_accounts()).rev() { + // self.account_manager.remove_account(index); + // } + // } + // }, + // ); + }) + .response } } @@ -166,26 +163,15 @@ fn account_card_ui() -> fn( } } -impl<'a> FromApp<'a> for AccountManagementView<'a> { - fn from_app(app: &'a mut crate::Damus) -> Self { - AccountManagementView::new( - &mut app.account_manager, - SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), - ) - } -} - -fn mobile_title() -> impl egui::Widget { - |ui: &mut egui::Ui| { - ui.vertical_centered(|ui| { - ui.label( - RichText::new(GlobalPopupType::AccountManagement.title()) - .text_style(NotedeckTextStyle::Heading2.text_style()) - .strong(), - ); - }) - .response - } +fn mobile_title(ui: &mut egui::Ui) -> egui::Response { + ui.vertical_centered(|ui| { + ui.label( + RichText::new("Account Management") + .text_style(NotedeckTextStyle::Heading2.text_style()) + .strong(), + ); + }) + .response } fn scroll_area() -> ScrollArea { diff --git a/src/ui/account_switcher.rs b/src/ui/account_switcher.rs index e786f2a..bae2f34 100644 --- a/src/ui/account_switcher.rs +++ b/src/ui/account_switcher.rs @@ -1,22 +1,43 @@ -use crate::{account_manager::UserAccount, colors::PINK, ui}; +use crate::{ + account_manager::{AccountManager, UserAccount}, + colors::PINK, + profile::DisplayName, + ui, Result, +}; use egui::{ Align, Button, Color32, Frame, Id, Image, Layout, Margin, RichText, Rounding, ScrollArea, Sense, Vec2, }; -use crate::account_manager::AccountManager; - -use super::{ - profile::{preview::SimpleProfilePreview, SimpleProfilePreviewController}, - state_in_memory::{STATE_ACCOUNT_MANAGEMENT, STATE_ACCOUNT_SWITCHER, STATE_SIDE_PANEL}, -}; +use super::profile::{preview::SimpleProfilePreview, SimpleProfilePreviewController}; pub struct AccountSelectionWidget<'a> { - account_manager: &'a mut AccountManager, + account_manager: &'a AccountManager, simple_preview_controller: SimpleProfilePreviewController<'a>, } +enum AccountSelectAction { + RemoveAccount { index: usize }, + SelectAccount { index: usize }, + OpenAccountManagement, +} + +#[derive(Default)] +struct AccountSelectResponse { + action: Option, +} + impl<'a> AccountSelectionWidget<'a> { + pub fn new( + account_manager: &'a AccountManager, + simple_preview_controller: SimpleProfilePreviewController<'a>, + ) -> Self { + AccountSelectionWidget { + account_manager, + simple_preview_controller, + } + } + pub fn ui(&'a mut self, ui: &mut egui::Ui) { if ui::is_mobile() { self.show_mobile(ui); @@ -25,33 +46,43 @@ impl<'a> AccountSelectionWidget<'a> { } } - fn show(&mut self, ui: &mut egui::Ui) { + fn show(&mut self, ui: &mut egui::Ui) -> AccountSelectResponse { + let mut res = AccountSelectResponse::default(); + let mut selected_index = self.account_manager.get_selected_account_index(); + Frame::none().outer_margin(8.0).show(ui, |ui| { - ui.add(top_section_widget()); + res = top_section_widget(ui); + scroll_area().show(ui, |ui| { - self.show_accounts(ui); + if let Some(index) = self.show_accounts(ui) { + selected_index = Some(index); + res.action = Some(AccountSelectAction::SelectAccount { index }); + } }); ui.add_space(8.0); ui.add(add_account_button()); - if let Some(account_index) = self.account_manager.get_selected_account_index() { - ui.add_space(8.0); - if self.handle_sign_out(ui, account_index) { - self.account_manager.remove_account(account_index); + if let Some(index) = selected_index { + if let Some(account) = self.account_manager.get_account(index) { + ui.add_space(8.0); + if self.handle_sign_out(ui, account) { + res.action = Some(AccountSelectAction::RemoveAccount { index }) + } } } ui.add_space(8.0); }); + + res } - fn handle_sign_out(&mut self, ui: &mut egui::Ui, account_index: usize) -> bool { - if let Some(account) = self.account_manager.get_account(account_index) { - if let Some(response) = self.sign_out_button(ui, account) { - return response.clicked(); - } + fn handle_sign_out(&mut self, ui: &mut egui::Ui, account: &UserAccount) -> bool { + if let Ok(response) = self.sign_out_button(ui, account) { + response.clicked() + } else { + false } - false } fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response { @@ -59,19 +90,19 @@ impl<'a> AccountSelectionWidget<'a> { todo!() } - fn show_accounts(&mut self, ui: &mut egui::Ui) { + fn show_accounts(&mut self, ui: &mut egui::Ui) -> Option { self.simple_preview_controller.view_profile_previews( self.account_manager, ui, account_switcher_card_ui(), - ); + ) } - fn sign_out_button(&self, ui: &mut egui::Ui, account: &UserAccount) -> Option { + fn sign_out_button(&self, ui: &mut egui::Ui, account: &UserAccount) -> Result { self.simple_preview_controller.show_with_nickname( ui, - &account.key.pubkey, - |ui, username| { + account.pubkey.bytes(), + |ui: &mut egui::Ui, username: &DisplayName| { let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png"); let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); let button = egui::Button::image_and_text( @@ -88,18 +119,6 @@ impl<'a> AccountSelectionWidget<'a> { } } -impl<'a> AccountSelectionWidget<'a> { - pub fn new( - account_manager: &'a mut AccountManager, - simple_preview_controller: SimpleProfilePreviewController<'a>, - ) -> Self { - AccountSelectionWidget { - account_manager, - simple_preview_controller, - } - } -} - fn account_switcher_card_ui() -> fn( ui: &mut egui::Ui, preview: SimpleProfilePreview, @@ -147,32 +166,29 @@ fn selection_widget() -> impl egui::Widget { } } -fn top_section_widget() -> impl egui::Widget { - |ui: &mut egui::Ui| { - ui.horizontal(|ui| { - ui.allocate_ui_with_layout( - Vec2::new(ui.available_size_before_wrap().x, 32.0), - Layout::left_to_right(egui::Align::Center), - |ui| ui.add(account_switcher_title()), - ); +fn top_section_widget(ui: &mut egui::Ui) -> AccountSelectResponse { + ui.horizontal(|ui| { + let mut resp = AccountSelectResponse::default(); - ui.allocate_ui_with_layout( - Vec2::new(ui.available_size_before_wrap().x, 32.0), - Layout::right_to_left(egui::Align::Center), - |ui| { - if ui.add(manage_accounts_button()).clicked() { - STATE_ACCOUNT_SWITCHER.set_state(ui.ctx(), false); - STATE_SIDE_PANEL.set_state( - ui.ctx(), - Some(ui::global_popup::GlobalPopupType::AccountManagement), - ); - STATE_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), true); - } - }, - ); - }) - .response - } + ui.allocate_ui_with_layout( + Vec2::new(ui.available_size_before_wrap().x, 32.0), + Layout::left_to_right(egui::Align::Center), + |ui| ui.add(account_switcher_title()), + ); + + ui.allocate_ui_with_layout( + Vec2::new(ui.available_size_before_wrap().x, 32.0), + Layout::right_to_left(egui::Align::Center), + |ui| { + if ui.add(manage_accounts_button()).clicked() { + resp.action = Some(AccountSelectAction::OpenAccountManagement); + } + }, + ); + + resp + }) + .inner } fn manage_accounts_button() -> egui::Button<'static> { @@ -227,7 +243,7 @@ mod previews { impl View for AccountSelectionPreview { fn ui(&mut self, ui: &mut egui::Ui) { AccountSelectionWidget::new( - &mut self.account_manager, + &self.account_manager, SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), ) .ui(ui); diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs deleted file mode 100644 index f1bd505..0000000 --- a/src/ui/global_popup.rs +++ /dev/null @@ -1,168 +0,0 @@ -use egui::{Align2, CentralPanel, RichText, Vec2, Window}; - -use crate::Damus; - -use super::{ - profile::SimpleProfilePreviewController, - state_in_memory::{STATE_ACCOUNT_MANAGEMENT, STATE_ACCOUNT_SWITCHER, STATE_SIDE_PANEL}, - AccountManagementView, AccountSelectionWidget, View, -}; - -#[derive(Clone, Copy, Debug)] -pub enum GlobalPopupType { - AccountManagement, - AccountSwitcher, -} - -static ACCOUNT_MANAGEMENT_TITLE: &str = "Manage accounts"; -static ACCOUNT_SWITCHER_TITLE: &str = "Account switcher"; - -impl GlobalPopupType { - pub fn title(&self) -> &'static str { - match self { - Self::AccountManagement => ACCOUNT_MANAGEMENT_TITLE, - Self::AccountSwitcher => ACCOUNT_SWITCHER_TITLE, - } - } -} - -pub trait FromApp<'a> { - fn from_app(app: &'a mut crate::Damus) -> Self - where - Self: Sized; -} - -fn title(title_str: &'static str) -> RichText { - RichText::new(title_str).size(24.0) -} - -fn overlay_window<'a>( - open: &'a mut bool, - window_size: Vec2, - title_str: &'static str, -) -> Window<'a> { - egui::Window::new(title(title_str)) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .collapsible(false) - .auto_sized() - .movable(false) - .open(open) - .default_size(window_size) -} - -fn account_switcher_window(open: &'_ mut bool) -> Window<'_> { - egui::Window::new("account switcher") - .title_bar(false) - .collapsible(false) - .anchor(Align2::LEFT_BOTTOM, Vec2::new(0.0, -52.0)) - .fixed_size(Vec2::new(360.0, 406.0)) - .open(open) - .movable(false) -} - -static MARGIN: Vec2 = Vec2 { x: 100.0, y: 100.0 }; - -pub struct DesktopGlobalPopup<'a> { - app: &'a mut Damus, -} - -impl<'a> View for DesktopGlobalPopup<'a> { - fn ui(&mut self, ui: &mut egui::Ui) { - DesktopGlobalPopup::global_popup(self.app, ui.ctx()) - } -} - -impl<'a> DesktopGlobalPopup<'a> { - pub fn new(app: &'a mut Damus) -> Self { - DesktopGlobalPopup { app } - } - pub fn global_popup(app: &mut Damus, ctx: &egui::Context) { - CentralPanel::default().show(ctx, |ui| { - if let Some(popup) = STATE_SIDE_PANEL.get_state(ctx) { - match popup { - GlobalPopupType::AccountManagement => { - Self::account_management(app, ctx, ui, popup.title()); - } - GlobalPopupType::AccountSwitcher => { - let mut show_account_switcher = STATE_ACCOUNT_SWITCHER.get_state(ctx); - if show_account_switcher { - STATE_ACCOUNT_MANAGEMENT.set_state(ctx, false); - account_switcher_window(&mut show_account_switcher).show(ctx, |ui| { - AccountSelectionWidget::new( - &mut app.account_manager, - SimpleProfilePreviewController::new( - &app.ndb, - &mut app.img_cache, - ), - ) - .ui(ui); - }); - } - } - } - } - }); - } - - fn account_management( - app: &mut Damus, - ctx: &egui::Context, - ui: &mut egui::Ui, - title: &'static str, - ) { - let available_size = ui.available_size(); - let window_size = available_size - MARGIN; - let mut show_account_management = STATE_ACCOUNT_MANAGEMENT.get_state(ctx); - if show_account_management { - overlay_window(&mut show_account_management, window_size, title).show(ctx, |ui| { - AccountManagementView::from_app(app).ui(ui); - }); - // user could have closed the window, set the new state in egui memory - STATE_ACCOUNT_MANAGEMENT.set_state(ctx, show_account_management); - } - } -} - -mod preview { - use crate::{ - test_data, - ui::{profile::SimpleProfilePreviewController, DesktopSidePanel, Preview, View}, - Damus, - }; - - use super::DesktopGlobalPopup; - - pub struct GlobalPopupPreview { - app: Damus, - } - - impl<'a> Preview for DesktopGlobalPopup<'a> { - type Prev = GlobalPopupPreview; - - fn preview() -> Self::Prev { - GlobalPopupPreview::new() - } - } - - impl GlobalPopupPreview { - fn new() -> Self { - let mut app = Damus::mock("."); - let accounts = test_data::get_test_accounts(); - accounts - .into_iter() - .for_each(|acc| app.account_manager.add_account(acc.key, || {})); - GlobalPopupPreview { app } - } - } - - impl View for GlobalPopupPreview { - fn ui(&mut self, ui: &mut egui::Ui) { - let mut panel = DesktopSidePanel::new( - &mut self.app.account_manager, - SimpleProfilePreviewController::new(&self.app.ndb, &mut self.app.img_cache), - ); - DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); - DesktopGlobalPopup::new(&mut self.app).ui(ui); - } - } -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d4bd5e4..c45644e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,19 +2,16 @@ pub mod account_login_view; pub mod account_management; pub mod account_switcher; pub mod anim; -pub mod global_popup; pub mod mention; pub mod note; pub mod preview; pub mod profile; pub mod relay; pub mod side_panel; -pub mod state_in_memory; pub mod username; pub use account_management::AccountManagementView; pub use account_switcher::AccountSelectionWidget; -pub use global_popup::DesktopGlobalPopup; pub use mention::Mention; pub use note::Note; pub use preview::{Preview, PreviewApp}; diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs index f154bad..2a556a3 100644 --- a/src/ui/profile/profile_preview_controller.rs +++ b/src/ui/profile/profile_preview_controller.rs @@ -1,10 +1,6 @@ -use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use crate::{ - account_manager::AccountManager, imgcache::ImageCache, - ui::state_in_memory::STATE_ACCOUNT_SWITCHER, DisplayName, -}; +use crate::{account_manager::AccountManager, imgcache::ImageCache, DisplayName, Result}; use super::{ preview::{get_display_name, get_profile_url, SimpleProfilePreview}, @@ -42,36 +38,48 @@ impl<'a> SimpleProfilePreviewController<'a> { let width = ui.available_width(); + let txn = if let Ok(txn) = Transaction::new(self.ndb) { + txn + } else { + return None; + }; + for i in 0..account_manager.num_accounts() { - if let Some(account) = account_manager.get_account(i) { - if let Ok(txn) = Transaction::new(self.ndb) { - let profile = self - .ndb - .get_profile_by_pubkey(&txn, account.key.pubkey.bytes()); + let account = if let Some(account) = account_manager.get_account(i) { + account + } else { + continue; + }; - if let Ok(profile) = profile { - let preview = SimpleProfilePreview::new(&profile, self.img_cache); + let profile = + if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, account.pubkey.bytes()) { + profile + } else { + continue; + }; - let is_selected = - if let Some(selected) = account_manager.get_selected_account_index() { - i == selected - } else { - false - }; + let preview = SimpleProfilePreview::new(&profile, self.img_cache); - if let Some(op) = add_preview_ui(ui, preview, width, is_selected) { - match op { - ProfilePreviewOp::RemoveAccount => { - if to_remove.is_none() { - to_remove = Some(Vec::new()); - } - to_remove.as_mut().unwrap().push(i); - } - ProfilePreviewOp::SwitchTo => account_manager.select_account(i), - } - } - }; + let is_selected = if let Some(selected) = account_manager.get_selected_account_index() { + i == selected + } else { + false + }; + + let op = if let Some(op) = add_preview_ui(ui, preview, width, is_selected) { + op + } else { + continue; + }; + + match op { + ProfilePreviewOp::RemoveAccount => { + if to_remove.is_none() { + to_remove = Some(Vec::new()); + } + to_remove.as_mut().unwrap().push(i); } + ProfilePreviewOp::SwitchTo => account_manager.select_account(i), } } @@ -80,7 +88,7 @@ impl<'a> SimpleProfilePreviewController<'a> { pub fn view_profile_previews( &mut self, - account_manager: &mut AccountManager, + account_manager: &AccountManager, ui: &mut egui::Ui, add_preview_ui: fn( ui: &mut egui::Ui, @@ -89,60 +97,64 @@ impl<'a> SimpleProfilePreviewController<'a> { is_selected: bool, index: usize, ) -> bool, - ) { + ) -> Option { let width = ui.available_width(); + let txn = if let Ok(txn) = Transaction::new(self.ndb) { + txn + } else { + return None; + }; + for i in 0..account_manager.num_accounts() { - if let Some(account) = account_manager.get_account(i) { - if let Ok(txn) = Transaction::new(self.ndb) { - let profile = self - .ndb - .get_profile_by_pubkey(&txn, account.key.pubkey.bytes()); + let account = if let Some(account) = account_manager.get_account(i) { + account + } else { + continue; + }; - if let Ok(profile) = profile { - let preview = SimpleProfilePreview::new(&profile, self.img_cache); + let profile = + if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, account.pubkey.bytes()) { + profile + } else { + continue; + }; - let is_selected = - if let Some(selected) = account_manager.get_selected_account_index() { - i == selected - } else { - false - }; + let preview = SimpleProfilePreview::new(&profile, self.img_cache); - if add_preview_ui(ui, preview, width, is_selected, i) { - account_manager.select_account(i); - STATE_ACCOUNT_SWITCHER.set_state(ui.ctx(), false); - } - } - } + let is_selected = if let Some(selected) = account_manager.get_selected_account_index() { + i == selected + } else { + false + }; + + if add_preview_ui(ui, preview, width, is_selected, i) { + return Some(i); } } + + None } pub fn show_with_nickname( &self, ui: &mut egui::Ui, - key: &Pubkey, + key: &[u8; 32], ui_element: fn(ui: &mut egui::Ui, username: &DisplayName) -> egui::Response, - ) -> Option { - if let Ok(txn) = Transaction::new(self.ndb) { - let profile = self.ndb.get_profile_by_pubkey(&txn, key.bytes()); - - if let Ok(profile) = profile { - return Some(ui_element(ui, &get_display_name(&profile))); - } - } - None + ) -> Result { + let txn = Transaction::new(self.ndb)?; + let profile = self.ndb.get_profile_by_pubkey(&txn, key)?; + Ok(ui_element(ui, &get_display_name(&profile))) } pub fn show_with_pfp( - &mut self, + self, ui: &mut egui::Ui, - key: &Pubkey, + key: &[u8; 32], ui_element: fn(ui: &mut egui::Ui, pfp: ProfilePic) -> egui::Response, ) -> Option { if let Ok(txn) = Transaction::new(self.ndb) { - let profile = self.ndb.get_profile_by_pubkey(&txn, key.bytes()); + let profile = self.ndb.get_profile_by_pubkey(&txn, key); if let Ok(profile) = profile { return Some(ui_element( diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs index a81b808..068f713 100644 --- a/src/ui/side_panel.rs +++ b/src/ui/side_panel.rs @@ -1,72 +1,75 @@ use egui::{Button, Layout, SidePanel, Vec2, Widget}; -use crate::{account_manager::AccountManager, ui::global_popup::GlobalPopupType}; +use crate::account_manager::AccountManager; -use super::{ - profile::SimpleProfilePreviewController, - state_in_memory::{STATE_ACCOUNT_SWITCHER, STATE_SIDE_PANEL}, - ProfilePic, View, -}; +use super::{profile::SimpleProfilePreviewController, ProfilePic, View}; pub struct DesktopSidePanel<'a> { - account_manager: &'a mut AccountManager, + selected_account: Option<&'a [u8; 32]>, simple_preview_controller: SimpleProfilePreviewController<'a>, } -static ID: &str = "left panel"; - -impl<'a> View for DesktopSidePanel<'a> { - fn ui(&mut self, ui: &mut egui::Ui) { - self.inner(ui); +impl<'a> Widget for DesktopSidePanel<'a> { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + self.show(ui) } } impl<'a> DesktopSidePanel<'a> { pub fn new( - account_manager: &'a mut AccountManager, + selected_account: Option<&'a [u8; 32]>, simple_preview_controller: SimpleProfilePreviewController<'a>, ) -> Self { DesktopSidePanel { - account_manager, + selected_account, simple_preview_controller, } } - pub fn inner(&mut self, ui: &mut egui::Ui) { - let dark_mode = ui.ctx().style().visuals.dark_mode; - let spacing_amt = 16.0; - ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| { - ui.add_space(spacing_amt); - if self.pfp_button(ui).clicked() { - STATE_SIDE_PANEL.set_state(ui.ctx(), Some(GlobalPopupType::AccountSwitcher)); - let previous_val = STATE_ACCOUNT_SWITCHER.get_state(ui.ctx()); - STATE_ACCOUNT_SWITCHER.set_state(ui.ctx(), !previous_val); - } - ui.add_space(spacing_amt); - ui.add(settings_button(dark_mode)); - ui.add_space(spacing_amt); - ui.add(add_column_button(dark_mode)); - ui.add_space(spacing_amt); - }); + pub fn panel() -> SidePanel { + egui::SidePanel::left("side_panel") + .resizable(false) + .exact_width(40.0) } - fn pfp_button(&mut self, ui: &mut egui::Ui) -> egui::Response { - if let Some(selected_account) = self.account_manager.get_selected_account() { - if let Some(response) = self.simple_preview_controller.show_with_pfp( - ui, - &selected_account.key.pubkey, - show_pfp(), - ) { + pub fn show(self, ui: &mut egui::Ui) -> egui::Response { + let dark_mode = ui.ctx().style().visuals.dark_mode; + let spacing_amt = 16.0; + + let inner = ui + .with_layout(Layout::bottom_up(egui::Align::Center), |ui| { + ui.spacing_mut().item_spacing.y = spacing_amt; + let pfp_resp = self.pfp_button(ui); + let settings_resp = ui.add(settings_button(dark_mode)); + let column_resp = ui.add(add_column_button(dark_mode)); + + if pfp_resp.clicked() || pfp_resp.hovered() { + egui::InnerResponse::new(SidePanelAction::Account, pfp_resp) + } else if settings_resp.clicked() || settings_resp.hovered() { + egui::InnerResponse::new(SidePanelAction::Settings, settings_resp) + } else if column_resp.clicked() || column_resp.hovered() { + egui::InnerResponse::new(SidePanelAction::Columns, column_resp) + } else { + egui::InnerResponse::new(SidePanelAction::Panel, pfp_resp) + } + }) + .inner; + + SidePanelResponse::new(inner.inner, inner.response) + } + + fn pfp_button(self, ui: &mut egui::Ui) -> egui::Response { + if let Some(selected_account) = self.selected_account { + if let Some(response) = + self.simple_preview_controller + .show_with_pfp(ui, selected_account, show_pfp()) + { return response; } } add_button_to_ui(ui, no_account_pfp()) } - - pub fn panel() -> SidePanel { - egui::SidePanel::left(ID).resizable(false).exact_width(40.0) - } } fn show_pfp() -> fn(ui: &mut egui::Ui, pfp: ProfilePic) -> egui::Response { @@ -126,10 +129,16 @@ mod preview { impl View for DesktopSidePanelPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let mut panel = DesktopSidePanel::new( - &mut self.account_manager, + let selected_account = self + .account_manager + .get_selected_account() + .map(|x| x.pubkey.bytes()); + + let panel = DesktopSidePanel::new( + selected_account, SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), ); + DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); } } diff --git a/src/ui/state_in_memory.rs b/src/ui/state_in_memory.rs deleted file mode 100644 index 09d37f1..0000000 --- a/src/ui/state_in_memory.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::any::Any; - -use super::global_popup::GlobalPopupType; - -/// StateInMemory is a helper struct for interacting with egui memory persisted data -#[derive(Clone)] -pub struct StateInMemory { - id: &'static str, - default_state: T, -} - -impl StateInMemory { - pub fn get_state(&self, ctx: &egui::Context) -> T { - ctx.data_mut(|d| { - d.get_temp(egui::Id::new(self.id)) - .unwrap_or(self.default_state.clone()) - }) - } - - pub fn set_state(&self, ctx: &egui::Context, new_val: T) { - ctx.data_mut(|d| d.insert_temp(egui::Id::new(self.id), new_val)); - } -} - -pub static STATE_ACCOUNT_MANAGEMENT: StateInMemory = StateInMemory:: { - id: ACCOUNT_MANAGEMENT_VIEW_STATE_ID, - default_state: false, -}; - -pub static STATE_SIDE_PANEL: StateInMemory> = - StateInMemory::> { - id: SIDE_PANEL_VIEW_STATE_ID, - default_state: None, - }; - -pub static STATE_ACCOUNT_SWITCHER: StateInMemory = StateInMemory:: { - id: ACCOUNT_SWITCHER_VIEW_STATE_ID, - default_state: false, -}; - -static ACCOUNT_MANAGEMENT_VIEW_STATE_ID: &str = "account management view state"; -static SIDE_PANEL_VIEW_STATE_ID: &str = "side panel view state"; -static ACCOUNT_SWITCHER_VIEW_STATE_ID: &str = "account switcher view state"; diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs index f7c8d06..c620308 100644 --- a/src/ui_preview/main.rs +++ b/src/ui_preview/main.rs @@ -3,8 +3,8 @@ use notedeck::app_creation::{ }; use notedeck::ui::account_login_view::AccountLoginView; use notedeck::ui::{ - AccountManagementView, AccountSelectionWidget, DesktopGlobalPopup, DesktopSidePanel, Preview, - PreviewApp, ProfilePic, ProfilePreview, RelayView, + AccountManagementView, AccountSelectionWidget, DesktopSidePanel, Preview, PreviewApp, + ProfilePic, ProfilePreview, RelayView, }; use std::env; @@ -89,6 +89,5 @@ async fn main() { AccountManagementView, AccountSelectionWidget, DesktopSidePanel, - DesktopGlobalPopup, ); } diff --git a/src/user_account.rs b/src/user_account.rs index 254be32..1bb911a 100644 --- a/src/user_account.rs +++ b/src/user_account.rs @@ -1,6 +1,9 @@ -use enostr::{FullKeypair, RelayPool}; +use enostr::Keypair; -pub struct UserAccount { - pub key: FullKeypair, - pub relays: RelayPool, -} +//pub struct UserAccount { +//pub key: Keypair, +//pub relays: RelayPool, +//pub relays: Vec, +//} + +pub type UserAccount = Keypair; From 92ce718e8b7fa715ede5d42c15920c95c60d4f3e Mon Sep 17 00:00:00 2001 From: William Casarin Date: Tue, 28 May 2024 15:00:57 -0700 Subject: [PATCH 18/18] side_panel: return more detailed side panel responses We should be treating all ui widgets as pure functions that do not mutate anything. This will make testing easier, as well as avoiding shared mutable references. Signed-off-by: William Casarin --- src/app.rs | 10 +++++----- src/ui/side_panel.rs | 23 +++++++++++++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2d48a83..345b65c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -867,16 +867,16 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: us .clip(true) .horizontal(|mut strip| { strip.cell(|ui| { - if DesktopSidePanel::new( + let side_panel = DesktopSidePanel::new( app.account_manager .get_selected_account() .map(|a| a.pubkey.bytes()), SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), ) - .show(ui) - .clicked() - { - // clicked pfp + .show(ui); + + if side_panel.response.clicked() { + info!("clicked {:?}", side_panel.action); } }); diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs index 068f713..0284de7 100644 --- a/src/ui/side_panel.rs +++ b/src/ui/side_panel.rs @@ -9,9 +9,28 @@ pub struct DesktopSidePanel<'a> { simple_preview_controller: SimpleProfilePreviewController<'a>, } +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SidePanelAction { + Panel, + Account, + Settings, + Columns, +} + +pub struct SidePanelResponse { + pub response: egui::Response, + pub action: SidePanelAction, +} + +impl SidePanelResponse { + fn new(action: SidePanelAction, response: egui::Response) -> Self { + SidePanelResponse { action, response } + } +} + impl<'a> Widget for DesktopSidePanel<'a> { fn ui(self, ui: &mut egui::Ui) -> egui::Response { - self.show(ui) + self.show(ui).response } } @@ -32,7 +51,7 @@ impl<'a> DesktopSidePanel<'a> { .exact_width(40.0) } - pub fn show(self, ui: &mut egui::Ui) -> egui::Response { + pub fn show(self, ui: &mut egui::Ui) -> SidePanelResponse { let dark_mode = ui.ctx().style().visuals.dark_mode; let spacing_amt = 16.0;