[PATCH notedeck v2 0/3] AccountManagementView

2 views
Skip to first unread message

kernelkind

unread,
May 5, 2024, 3:38:50 PMMay 5
to pat...@damus.io, kernelkind
V1 -> V2:
- Make display name display vertically in SimpleProfilePreview
- add show_accounts_mobile for AccountManagementView
- remove Transaction as member variable from
SimpleProfilePreviewController. A new txn is now created for every
ndb profile lookup
- account_management_view.rs was moved to src/ui/account_management.rs

A seperate patch will be sent for removing the nostr-sdk dependency
after this feature is accepted, if that's ok.

kernelkind (3):
Add SimpleProfilePreview
AccountManagementView
AccountSelectionWidget

src/account_manager.rs | 136 +++++++++++++++
src/key_storage.rs | 48 +++++
src/lib.rs | 4 +
src/relay_generation.rs | 54 ++++++
src/relay_pool_manager.rs | 2 +-
src/ui/account_management.rs | 330 +++++++++++++++++++++++++++++++++++
src/ui/mod.rs | 2 +
src/ui/profile/preview.rs | 131 +++++++++-----
src/ui_preview/main.rs | 9 +-
src/user_account.rs | 6 +
10 files changed, 678 insertions(+), 44 deletions(-)
create mode 100644 src/account_manager.rs
create mode 100644 src/key_storage.rs
create mode 100644 src/relay_generation.rs
create mode 100644 src/ui/account_management.rs
create mode 100644 src/user_account.rs


base-commit: 30e9aa5357d2e220a8a5b57d5617a0426443cd5e
--
2.39.3 (Apple Git-146)

kernelkind

unread,
May 5, 2024, 3:38:57 PMMay 5
to pat...@damus.io, kernelkind
Preview that only contains the pfp, display name, and username

Signed-off-by: kernelkind <kerne...@gmail.com>
---
src/ui/profile/preview.rs | 131 ++++++++++++++++++++++++++------------
1 file changed, 90 insertions(+), 41 deletions(-)

diff --git a/src/ui/profile/preview.rs b/src/ui/profile/preview.rs
index 43354810884a..a6b3194c1c23 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::{RichText, Sense};
+use egui::{Frame, Layout, RichText, Sense, Vec2, Widget};
use egui_extras::Size;
use nostrdb::ProfileRecord;

@@ -63,46 +63,10 @@ impl<'a, 'cache> ProfilePreview<'a, 'cache> {
}

fn body(self, ui: &mut egui::Ui) {
- let name = if let Some(name) = crate::profile::get_profile_name(self.profile) {
- name
- } else {
- DisplayName::One("??")
- };
-
crate::ui::padding(12.0, ui, |ui| {
- let url = if let Some(url) = self.profile.record().profile().and_then(|p| p.picture()) {
- url
- } else {
- ProfilePic::no_pfp_url()
- };
-
- ui.add(ProfilePic::new(self.cache, url).size(80.0));
-
- match name {
- DisplayName::One(n) => {
- ui.label(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style()));
- }
-
- DisplayName::Both {
- display_name,
- username,
- } => {
- ui.label(
- RichText::new(display_name)
- .text_style(NotedeckTextStyle::Heading3.text_style()),
- );
-
- ui.label(
- RichText::new(format!("@{}", username))
- .size(12.0)
- .color(colors::MID_GRAY),
- );
- }
- }
-
- if let Some(about) = self.profile.record().profile().and_then(|p| p.about()) {
- ui.label(about);
- }
+ ui.add(ProfilePic::new(self.cache, get_profile_url(self.profile)).size(80.0));
+ ui.add(display_name_widget(get_display_name(self.profile), false));
+ ui.add(about_section_widget(self.profile));
});
}
}
@@ -120,11 +84,38 @@ impl<'a, 'cache> egui::Widget for ProfilePreview<'a, 'cache> {
}
}

+pub struct SimpleProfilePreview<'a, 'cache> {
+ profile: &'a ProfileRecord<'a>,
+ cache: &'cache mut ImageCache,
+}
+
+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> {
+ fn ui(self, ui: &mut egui::Ui) -> egui::Response {
+ Frame::none()
+ .show(ui, |ui| {
+ ui.add(ProfilePic::new(self.cache, get_profile_url(self.profile)).size(48.0));
+ ui.vertical(|ui| {
+ ui.add(display_name_widget(get_display_name(self.profile), true));
+ });
+ })
+ .response
+ }
+}
+
mod previews {
use super::*;
use crate::test_data::test_profile_record;
use crate::ui::{Preview, View};
- use egui::Widget;

pub struct ProfilePreviewPreview<'a> {
profile: ProfileRecord<'a>,
@@ -160,3 +151,61 @@ mod previews {
}
}
}
+
+fn get_display_name<'a>(profile: &'a ProfileRecord<'a>) -> DisplayName<'a> {
+ if let Some(name) = crate::profile::get_profile_name(profile) {
+ name
+ } else {
+ DisplayName::One("??")
+ }
+}
+
+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 {
+ ProfilePic::no_pfp_url()
+ }
+}
+
+fn display_name_widget(
+ display_name: DisplayName<'_>,
+ add_placeholder_space: bool,
+) -> impl egui::Widget + '_ {
+ move |ui: &mut egui::Ui| match display_name {
+ DisplayName::One(n) => {
+ let name_response =
+ ui.label(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style()));
+ if add_placeholder_space {
+ ui.add_space(16.0);
+ }
+ name_response
+ }
+
+ DisplayName::Both {
+ display_name,
+ username,
+ } => {
+ ui.label(
+ RichText::new(display_name).text_style(NotedeckTextStyle::Heading3.text_style()),
+ );
+
+ ui.label(
+ RichText::new(format!("@{}", username))
+ .size(12.0)
+ .color(colors::MID_GRAY),
+ )
+ }
+ }
+}
+
+fn about_section_widget<'a>(profile: &'a ProfileRecord<'a>) -> impl egui::Widget + 'a {
+ |ui: &mut egui::Ui| {
+ if let Some(about) = profile.record().profile().and_then(|p| p.about()) {
+ ui.label(about)
+ } else {
+ // need any Response so we dont need an Option
+ ui.allocate_response(egui::Vec2::ZERO, egui::Sense::hover())
+ }
+ }
+}
--
2.39.3 (Apple Git-146)

kernelkind

unread,
May 5, 2024, 3:39:00 PMMay 5
to pat...@damus.io, kernelkind
View used to add and remove accounts from the app

Signed-off-by: kernelkind <kerne...@gmail.com>
---
src/account_manager.rs | 107 ++++++++++++
src/key_storage.rs | 48 +++++
src/lib.rs | 4 +
src/relay_generation.rs | 54 ++++++
src/relay_pool_manager.rs | 2 +-
src/ui/account_management.rs | 328 +++++++++++++++++++++++++++++++++++
src/ui/mod.rs | 2 +
src/ui_preview/main.rs | 7 +-
src/user_account.rs | 6 +
9 files changed, 555 insertions(+), 3 deletions(-)
create mode 100644 src/account_manager.rs
create mode 100644 src/key_storage.rs
create mode 100644 src/relay_generation.rs
create mode 100644 src/ui/account_management.rs
create mode 100644 src/user_account.rs

diff --git a/src/account_manager.rs b/src/account_manager.rs
new file mode 100644
index 000000000000..2878ee453388
--- /dev/null
+++ b/src/account_manager.rs
@@ -0,0 +1,107 @@
+use nostr_sdk::Keys;
+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<'a>,
+ ui: &mut egui::Ui,
+ edit_mode: bool,
+ add_preview_ui: fn(
+ ui: &mut egui::Ui,
+ preview: SimpleProfilePreview,
+ edit_mode: bool,
+ ) -> bool,
+ ) -> Option<Vec<usize>> {
+ let mut to_remove: Option<Vec<usize>> = 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.public_key().to_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
+ }
+}
+
+/// 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<UserAccount>,
+ key_store: KeyStorage,
+ relay_generator: RelayGenerator,
+}
+
+impl<'a> AccountManager<'a> {
+ pub fn new(
+ accounts: &'a mut Vec<UserAccount>,
+ key_store: KeyStorage,
+ relay_generator: RelayGenerator,
+ ) -> Self {
+ AccountManager {
+ accounts,
+ key_store,
+ relay_generator,
+ }
+ }
+
+ pub fn get_accounts(&'a self) -> &'a Vec<UserAccount> {
+ self.accounts
+ }
+
+ pub fn get_account(&'a self, index: usize) -> Option<&'a UserAccount> {
+ self.accounts.get(index)
+ }
+
+ pub fn remove_account(&mut self, index: usize) {
+ if let Some(account) = self.accounts.get(index) {
+ self.key_store.remove_key(&account.key);
+ }
+ if index < self.accounts.len() {
+ self.accounts.remove(index);
+ }
+ }
+
+ pub fn add_account(&'a mut self, key: Keys, ctx: &egui::Context) {
+ self.key_store.add_key(&key);
+ let relays = self.relay_generator.generate_relays_for(&key, ctx);
+ let account = UserAccount { key, relays };
+
+ self.accounts.push(account)
+ }
+
+ pub fn num_accounts(&self) -> usize {
+ self.accounts.len()
+ }
+}
diff --git a/src/key_storage.rs b/src/key_storage.rs
new file mode 100644
index 000000000000..8bf0f407398b
--- /dev/null
+++ b/src/key_storage.rs
@@ -0,0 +1,48 @@
+use nostr_sdk::Keys;
+
+pub enum KeyStorage {
+ None,
+ // TODO:
+ // Linux,
+ // Windows,
+ // Android,
+}
+
+impl KeyStorage {
+ pub fn get_keys(&self) -> Result<Vec<Keys>, KeyStorageError> {
+ match self {
+ Self::None => Ok(Vec::new()),
+ }
+ }
+
+ pub fn add_key(&self, key: &Keys) -> Result<(), KeyStorageError> {
+ match self {
+ Self::None => Ok(()),
+ }
+ }
+
+ pub fn remove_key(&self, key: &Keys) -> Result<(), KeyStorageError> {
+ match self {
+ Self::None => Ok(()),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum KeyStorageError<'a> {
+ Retrieval,
+ Addition(&'a Keys),
+ Removal(&'a Keys),
+}
+
+impl std::fmt::Display for KeyStorageError<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Self::Retrieval => write!(f, "Failed to retrieve keys."),
+ Self::Addition(key) => write!(f, "Failed to add key: {:?}", key.public_key()),
+ Self::Removal(key) => write!(f, "Failed to remove key: {:?}", key.public_key()),
+ }
+ }
+}
+
+impl std::error::Error for KeyStorageError<'_> {}
diff --git a/src/lib.rs b/src/lib.rs
index 51859df6624c..2a36d0faefba 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,6 +4,7 @@ mod error;
//mod note;
//mod block;
mod abbrev;
+pub mod account_manager;
pub mod app_creation;
mod app_style;
mod colors;
@@ -13,9 +14,11 @@ mod frame_history;
mod images;
mod imgcache;
mod key_parsing;
+mod key_storage;
pub mod login_manager;
mod notecache;
mod profile;
+mod relay_generation;
pub mod relay_pool_manager;
mod result;
mod test_data;
@@ -23,6 +26,7 @@ mod time;
mod timecache;
mod timeline;
pub mod ui;
+mod user_account;

#[cfg(test)]
#[macro_use]
diff --git a/src/relay_generation.rs b/src/relay_generation.rs
new file mode 100644
index 000000000000..eedc94734db4
--- /dev/null
+++ b/src/relay_generation.rs
@@ -0,0 +1,54 @@
+use crate::relay_pool_manager::create_wakeup;
+use enostr::RelayPool;
+use nostr_sdk::Keys;
+use tracing::error;
+
+pub enum RelayGenerator {
+ GossipModel,
+ Nip65,
+ Constant,
+}
+
+impl RelayGenerator {
+ pub fn generate_relays_for(&self, key: &Keys, ctx: &egui::Context) -> RelayPool {
+ match self {
+ Self::GossipModel => generate_relays_gossip(key, ctx),
+ Self::Nip65 => generate_relays_nip65(key, ctx),
+ Self::Constant => generate_constant_relays(ctx),
+ }
+ }
+}
+
+fn generate_relays_gossip(key: &Keys, ctx: &egui::Context) -> RelayPool {
+ todo!()
+}
+
+fn generate_relays_nip65(key: &Keys, ctx: &egui::Context) -> RelayPool {
+ todo!()
+}
+
+fn generate_constant_relays(ctx: &egui::Context) -> 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)
+ }
+ if let Err(e) = pool.add_url("wss://relay.damus.io".to_string(), wakeup.clone()) {
+ error!("{:?}", e)
+ }
+ if let Err(e) = pool.add_url("wss://pyramid.fiatjaf.com".to_string(), wakeup.clone()) {
+ error!("{:?}", e)
+ }
+ if let Err(e) = pool.add_url("wss://nos.lol".to_string(), wakeup.clone()) {
+ error!("{:?}", e)
+ }
+ if let Err(e) = pool.add_url("wss://nostr.wine".to_string(), wakeup.clone()) {
+ error!("{:?}", e)
+ }
+ if let Err(e) = pool.add_url("wss://purplepag.es".to_string(), wakeup) {
+ error!("{:?}", e)
+ }
+
+ pool
+}
diff --git a/src/relay_pool_manager.rs b/src/relay_pool_manager.rs
index 9550a3645c3e..df339e46eef0 100644
--- a/src/relay_pool_manager.rs
+++ b/src/relay_pool_manager.rs
@@ -46,7 +46,7 @@ impl<'a> RelayPoolManager<'a> {
}
}

-fn create_wakeup(ctx: &egui::Context) -> impl Fn() + Send + Sync + Clone + 'static {
+pub fn create_wakeup(ctx: &egui::Context) -> impl Fn() + Send + Sync + Clone + 'static {
let ctx = ctx.clone();
move || {
ctx.request_repaint();
diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs
new file mode 100644
index 000000000000..336da9936b46
--- /dev/null
+++ b/src/ui/account_management.rs
@@ -0,0 +1,328 @@
+use egui::{Align, Align2, Button, Frame, Layout, Margin, RichText, ScrollArea, Vec2, Window};
+
+use crate::{
+ account_manager::{AccountManager, SimpleProfilePreviewController},
+ app_style::NotedeckTextStyle,
+ ui::{self, Preview, View},
+};
+
+pub struct AccountManagementView<'a> {
+ account_manager: AccountManager<'a>,
+ simple_preview_controller: SimpleProfilePreviewController<'a>,
+ edit_mode: &'a mut bool,
+}
+
+impl<'a> View for AccountManagementView<'a> {
+ fn ui(&mut self, ui: &mut egui::Ui) {
+ if ui::is_mobile(ui.ctx()) {
+ self.show_mobile(ui);
+ } else {
+ self.show(ui);
+ }
+ }
+}
+
+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);
+ });
+ }
+
+ 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,
+ *self.edit_mode,
+ |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
+ },
+ );
+
+ 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,
+ *self.edit_mode,
+ |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
+ },
+ );
+
+ self.maybe_remove_accounts(maybe_remove);
+ },
+ );
+ });
+ }
+
+ fn maybe_remove_accounts(&mut self, account_indices: Option<Vec<usize>>) {
+ if let Some(to_remove) = account_indices {
+ to_remove
+ .iter()
+ .for_each(|index| self.account_manager.remove_account(*index));
+ }
+ }
+
+ fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response {
+ egui::CentralPanel::default()
+ .show(ui.ctx(), |ui| {
+ ui.add(title());
+ ui.add(self.buttons_widget());
+ ui.add_space(8.0);
+ self.show_accounts_mobile(ui);
+ })
+ .response
+ }
+
+ fn 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 *self.edit_mode {
+ if ui.add(done_account_button()).clicked() {
+ *self.edit_mode = false;
+ }
+ } else if ui.add(edit_account_button()).clicked() {
+ *self.edit_mode = 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
+ }
+ },
+ );
+ })
+ .response
+ }
+ }
+}
+
+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 title() -> impl egui::Widget {
+ |ui: &mut egui::Ui| {
+ ui.vertical_centered(|ui| {
+ ui.label(
+ RichText::new("Accounts")
+ .text_style(NotedeckTextStyle::Heading2.text_style())
+ .strong(),
+ );
+ })
+ .response
+ }
+}
+
+fn scroll_area() -> ScrollArea {
+ egui::ScrollArea::vertical()
+ .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
+ .auto_shrink([false; 2])
+}
+
+fn add_account_button() -> Button<'static> {
+ Button::new("Add Account").min_size(Vec2::new(0.0, 32.0))
+}
+
+fn edit_account_button() -> Button<'static> {
+ Button::new("Edit").min_size(Vec2::new(0.0, 32.0))
+}
+
+fn done_account_button() -> Button<'static> {
+ Button::new("Done").min_size(Vec2::new(0.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)
+}
+
+// PREVIEWS
+
+mod preview {
+ use nostr_sdk::{Keys, PublicKey};
+ use nostrdb::{Config, Ndb};
+
+ use super::*;
+ use crate::key_storage::KeyStorage;
+ use crate::relay_generation::RelayGenerator;
+ use crate::{account_manager::UserAccount, imgcache::ImageCache, test_data};
+ use std::path::Path;
+
+ pub struct AccountManagementPreview {
+ accounts: Vec<UserAccount>,
+ ndb: Ndb,
+ img_cache: ImageCache,
+ edit_mode: bool,
+ }
+
+ impl AccountManagementPreview {
+ fn new() -> Self {
+ let account_hexes = [
+ "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681",
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
+ "bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91",
+ "5c10ed0678805156d39ef1ef6d46110fe1e7e590ae04986ccf48ba1299cb53e2",
+ "4c96d763eb2fe01910f7e7220b7c7ecdbe1a70057f344b9f79c28af080c3ee30",
+ "edf16b1dd61eab353a83af470cc13557029bff6827b4cb9b7fc9bdb632a2b8e6",
+ "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681",
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
+ ];
+
+ let accounts: Vec<UserAccount> = account_hexes
+ .iter()
+ .map(|account_hex| {
+ let key = Keys::from_public_key(PublicKey::from_hex(account_hex).unwrap());
+
+ UserAccount {
+ key,
+ relays: test_data::sample_pool(),
+ }
+ })
+ .collect();
+
+ 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);
+
+ AccountManagementPreview {
+ accounts,
+ ndb,
+ img_cache,
+ edit_mode: false,
+ }
+ }
+ }
+
+ impl View for AccountManagementPreview {
+ fn ui(&mut self, ui: &mut egui::Ui) {
+ let account_manager = AccountManager::new(
+ &mut self.accounts,
+ KeyStorage::None,
+ RelayGenerator::Constant,
+ );
+
+ AccountManagementView::new(
+ account_manager,
+ SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache),
+ &mut self.edit_mode,
+ )
+ .ui(ui);
+ }
+ }
+
+ impl<'a> Preview for AccountManagementView<'a> {
+ type Prev = AccountManagementPreview;
+
+ fn preview() -> Self::Prev {
+ AccountManagementPreview::new()
+ }
+ }
+}
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index 11db0bed039c..525ceebf4014 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -1,4 +1,5 @@
pub mod account_login_view;
+pub mod account_management;
pub mod anim;
pub mod mention;
pub mod note;
@@ -7,6 +8,7 @@ pub mod profile;
pub mod relay;
pub mod username;

+pub use account_management::AccountManagementView;
pub use mention::Mention;
pub use note::Note;
pub use preview::{Preview, PreviewApp};
diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs
index ce4d160ac400..f4c2769e4bbc 100644
--- a/src/ui_preview/main.rs
+++ b/src/ui_preview/main.rs
@@ -2,7 +2,9 @@ use notedeck::app_creation::{
generate_mobile_emulator_native_options, generate_native_options, setup_cc,
};
use notedeck::ui::account_login_view::AccountLoginView;
-use notedeck::ui::{Preview, PreviewApp, ProfilePic, ProfilePreview, RelayView};
+use notedeck::ui::{
+ AccountManagementView, Preview, PreviewApp, ProfilePic, ProfilePreview, RelayView,
+};
use std::env;

struct PreviewRunner {
@@ -82,6 +84,7 @@ async fn main() {
RelayView,
AccountLoginView,
ProfilePreview,
- ProfilePic
+ ProfilePic,
+ AccountManagementView,
);
}
diff --git a/src/user_account.rs b/src/user_account.rs
new file mode 100644
index 000000000000..1f17e7f64151
--- /dev/null
+++ b/src/user_account.rs
@@ -0,0 +1,6 @@
+use enostr::RelayPool;
+
+pub struct UserAccount {
+ pub key: nostr_sdk::Keys,
+ pub relays: RelayPool,

kernelkind

unread,
May 5, 2024, 3:39:03 PMMay 5
to pat...@damus.io, kernelkind
Will be useful for selecting an account for the 'Add Column' view

Signed-off-by: kernelkind <kerne...@gmail.com>
---
src/account_manager.rs | 29 +++++++++++++++++++++++++++++
src/ui/account_management.rs | 4 +++-
src/ui/mod.rs | 2 +-
src/ui_preview/main.rs | 4 +++-
4 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/src/account_manager.rs b/src/account_manager.rs
index 2878ee453388..bdc2c993a09f 100644
--- a/src/account_manager.rs
+++ b/src/account_manager.rs
@@ -53,6 +53,35 @@ impl<'a> SimpleProfilePreviewController<'a> {

to_remove
}
+
+ pub fn view_profile_previews(
+ &mut self,
+ account_manager: &'a AccountManager<'a>,
+ ui: &mut egui::Ui,
+ add_preview_ui: fn(ui: &mut egui::Ui, preview: SimpleProfilePreview, index: usize) -> bool,
+ ) -> Option<usize> {
+ let mut clicked_at: Option<usize> = 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.public_key().to_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
+ }
}

/// The interface for managing the user's accounts.
diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs
index 336da9936b46..3831fd296128 100644
--- a/src/ui/account_management.rs
+++ b/src/ui/account_management.rs
@@ -1,4 +1,6 @@
-use egui::{Align, Align2, Button, Frame, Layout, Margin, RichText, ScrollArea, Vec2, Window};
+use egui::{
+ Align, Align2, Button, Frame, Layout, Margin, RichText, ScrollArea, Vec2, Window,
+};

use crate::{
account_manager::{AccountManager, SimpleProfilePreviewController},
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index 525ceebf4014..5b356f9867d3 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -8,7 +8,7 @@ pub mod profile;
pub mod relay;
pub mod username;

-pub use account_management::AccountManagementView;
+pub use account_management::{AccountManagementView, AccountSelectionWidget};
pub use mention::Mention;
pub use note::Note;
pub use preview::{Preview, PreviewApp};
diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs
index f4c2769e4bbc..d2bf9a0bbfcf 100644
--- a/src/ui_preview/main.rs
+++ b/src/ui_preview/main.rs
@@ -3,7 +3,8 @@ use notedeck::app_creation::{
};
use notedeck::ui::account_login_view::AccountLoginView;
use notedeck::ui::{
- AccountManagementView, Preview, PreviewApp, ProfilePic, ProfilePreview, RelayView,
+ AccountManagementView, AccountSelectionWidget, Preview, PreviewApp, ProfilePic, ProfilePreview,
+ RelayView,
};
use std::env;

@@ -86,5 +87,6 @@ async fn main() {
ProfilePreview,
ProfilePic,
AccountManagementView,
+ AccountSelectionWidget,
);
}
--
2.39.3 (Apple Git-146)

kernelkind

unread,
May 9, 2024, 3:00:35 PMMay 9
to pat...@damus.io
I don't know how I overlooked this, but I just realized the AccountSelectionWidget didn't make it from V1 to V2, so expect a V3 with that change... :facepalm:.
Reply all
Reply to author
Forward
0 new messages