Merge remote-tracking branch 'pr/80'
This commit is contained in:
BIN
assets/icons/add_account_icon_4x.png
Normal file
BIN
assets/icons/add_account_icon_4x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/icons/add_column_dark_4x.png
Normal file
BIN
assets/icons/add_column_dark_4x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/icons/plus_icon_4x.png
Normal file
BIN
assets/icons/plus_icon_4x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 340 B |
BIN
assets/icons/select_icon_3x.png
Normal file
BIN
assets/icons/select_icon_3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/icons/settings_dark_4x.png
Normal file
BIN
assets/icons/settings_dark_4x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/icons/signout_icon_4x.png
Normal file
BIN
assets/icons/signout_icon_4x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -8,7 +8,7 @@ pub struct Keypair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 cloned_secret_key = secret_key.clone();
|
||||||
let nostr_keys = nostr::Keys::new(secret_key);
|
let nostr_keys = nostr::Keys::new(secret_key);
|
||||||
Keypair {
|
Keypair {
|
||||||
@@ -17,6 +17,10 @@ impl Keypair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new(pubkey: Pubkey, secret_key: Option<SecretKey>) -> Self {
|
||||||
|
Keypair { pubkey, secret_key }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn only_pubkey(pubkey: Pubkey) -> Self {
|
pub fn only_pubkey(pubkey: Pubkey) -> Self {
|
||||||
Keypair {
|
Keypair {
|
||||||
pubkey,
|
pubkey,
|
||||||
|
|||||||
@@ -1,136 +1,92 @@
|
|||||||
use enostr::FullKeypair;
|
use std::cmp::Ordering;
|
||||||
use nostrdb::{Ndb, Transaction};
|
|
||||||
|
|
||||||
|
use enostr::Keypair;
|
||||||
|
|
||||||
|
use crate::key_storage::KeyStorage;
|
||||||
pub use crate::user_account::UserAccount;
|
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.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<'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.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The interface for managing the user's accounts.
|
/// The interface for managing the user's accounts.
|
||||||
/// Represents all user-facing operations related to account management.
|
/// Represents all user-facing operations related to account management.
|
||||||
pub struct AccountManager<'a> {
|
pub struct AccountManager {
|
||||||
accounts: &'a mut Vec<UserAccount>,
|
currently_selected_account: Option<usize>,
|
||||||
|
accounts: Vec<UserAccount>,
|
||||||
key_store: KeyStorage,
|
key_store: KeyStorage,
|
||||||
relay_generator: RelayGenerator,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AccountManager<'a> {
|
impl AccountManager {
|
||||||
pub fn new(
|
pub fn new(currently_selected_account: Option<usize>, key_store: KeyStorage) -> Self {
|
||||||
accounts: &'a mut Vec<UserAccount>,
|
let accounts = key_store.get_keys().unwrap_or_default();
|
||||||
key_store: KeyStorage,
|
|
||||||
relay_generator: RelayGenerator,
|
|
||||||
) -> Self {
|
|
||||||
AccountManager {
|
AccountManager {
|
||||||
|
currently_selected_account,
|
||||||
accounts,
|
accounts,
|
||||||
key_store,
|
key_store,
|
||||||
relay_generator,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_accounts(&'a self) -> &'a Vec<UserAccount> {
|
pub fn get_accounts(&self) -> &Vec<UserAccount> {
|
||||||
self.accounts
|
&self.accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_account(&'a self, index: usize) -> Option<&'a UserAccount> {
|
pub fn get_account(&self, ind: usize) -> Option<&UserAccount> {
|
||||||
self.accounts.get(index)
|
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) {
|
pub fn remove_account(&mut self, index: usize) {
|
||||||
if let Some(account) = self.accounts.get(index) {
|
if let Some(account) = self.accounts.get(index) {
|
||||||
let _ = self.key_store.remove_key(&account.key);
|
let _ = self.key_store.remove_key(account);
|
||||||
}
|
|
||||||
if index < self.accounts.len() {
|
|
||||||
self.accounts.remove(index);
|
self.accounts.remove(index);
|
||||||
|
|
||||||
|
if let Some(selected_index) = self.currently_selected_account {
|
||||||
|
match selected_index.cmp(&index) {
|
||||||
|
Ordering::Greater => {
|
||||||
|
self.select_account(selected_index - 1);
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
self.clear_selected_account();
|
||||||
|
}
|
||||||
|
Ordering::Less => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_account(&'a mut self, key: FullKeypair, ctx: &egui::Context) {
|
pub fn add_account(&mut self, account: Keypair) {
|
||||||
let _ = self.key_store.add_key(&key);
|
let _ = self.key_store.add_key(&account);
|
||||||
let relays = self.relay_generator.generate_relays_for(&key.pubkey, ctx);
|
|
||||||
let account = UserAccount { key, relays };
|
|
||||||
|
|
||||||
self.accounts.push(account)
|
self.accounts.push(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn num_accounts(&self) -> usize {
|
pub fn num_accounts(&self) -> usize {
|
||||||
self.accounts.len()
|
self.accounts.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_account_index(&self) -> Option<usize> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_selected_account(&mut self) {
|
||||||
|
self.currently_selected_account = None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
src/app.rs
64
src/app.rs
@@ -1,12 +1,15 @@
|
|||||||
|
use crate::account_manager::AccountManager;
|
||||||
use crate::app_creation::setup_cc;
|
use crate::app_creation::setup_cc;
|
||||||
use crate::app_style::user_requested_visuals_change;
|
use crate::app_style::user_requested_visuals_change;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::frame_history::FrameHistory;
|
use crate::frame_history::FrameHistory;
|
||||||
use crate::imgcache::ImageCache;
|
use crate::imgcache::ImageCache;
|
||||||
use crate::notecache::{CachedNote, NoteCache};
|
use crate::notecache::{CachedNote, NoteCache};
|
||||||
|
use crate::route::Route;
|
||||||
use crate::timeline;
|
use crate::timeline;
|
||||||
use crate::timeline::{NoteRef, Timeline, ViewFilter};
|
use crate::timeline::{NoteRef, Timeline, ViewFilter};
|
||||||
use crate::ui::is_mobile;
|
use crate::ui::profile::SimpleProfilePreviewController;
|
||||||
|
use crate::ui::{is_mobile, DesktopSidePanel};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
use egui::{Context, Frame, Style};
|
use egui::{Context, Frame, Style};
|
||||||
@@ -36,6 +39,8 @@ pub struct Damus {
|
|||||||
note_cache: NoteCache,
|
note_cache: NoteCache,
|
||||||
pool: RelayPool,
|
pool: RelayPool,
|
||||||
|
|
||||||
|
/// global navigation for account management popups, etc.
|
||||||
|
nav: Vec<Route>,
|
||||||
pub textmode: bool,
|
pub textmode: bool,
|
||||||
|
|
||||||
pub timelines: Vec<Timeline>,
|
pub timelines: Vec<Timeline>,
|
||||||
@@ -43,6 +48,7 @@ pub struct Damus {
|
|||||||
|
|
||||||
pub img_cache: ImageCache,
|
pub img_cache: ImageCache,
|
||||||
pub ndb: Ndb,
|
pub ndb: Ndb,
|
||||||
|
pub account_manager: AccountManager,
|
||||||
|
|
||||||
frame_history: crate::frame_history::FrameHistory,
|
frame_history: crate::frame_history::FrameHistory,
|
||||||
}
|
}
|
||||||
@@ -647,14 +653,47 @@ impl Damus {
|
|||||||
img_cache: ImageCache::new(imgcache_dir),
|
img_cache: ImageCache::new(imgcache_dir),
|
||||||
note_cache: NoteCache::default(),
|
note_cache: NoteCache::default(),
|
||||||
selected_timeline: 0,
|
selected_timeline: 0,
|
||||||
|
nav: Vec::with_capacity(6),
|
||||||
timelines,
|
timelines,
|
||||||
textmode: false,
|
textmode: false,
|
||||||
ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"),
|
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,
|
||||||
|
),
|
||||||
//compose: "".to_string(),
|
//compose: "".to_string(),
|
||||||
frame_history: FrameHistory::default(),
|
frame_history: FrameHistory::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mock<P: AsRef<Path>>(data_path: P) -> Self {
|
||||||
|
let mut timelines: Vec<Timeline> = 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,
|
||||||
|
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),
|
||||||
|
frame_history: FrameHistory::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn note_cache_mut(&mut self) -> &mut NoteCache {
|
pub fn note_cache_mut(&mut self) -> &mut NoteCache {
|
||||||
&mut self.note_cache
|
&mut self.note_cache
|
||||||
}
|
}
|
||||||
@@ -815,14 +854,6 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) {
|
|||||||
Size::remainder()
|
Size::remainder()
|
||||||
};
|
};
|
||||||
|
|
||||||
if app.timelines.len() == 1 {
|
|
||||||
main_panel(&ctx.style()).show(ctx, |ui| {
|
|
||||||
timeline::timeline_view(ui, app, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
main_panel(&ctx.style()).show(ctx, |ui| {
|
main_panel(&ctx.style()).show(ctx, |ui| {
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
if need_scroll {
|
if need_scroll {
|
||||||
@@ -837,9 +868,24 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) {
|
|||||||
|
|
||||||
fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: usize) {
|
fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: usize) {
|
||||||
StripBuilder::new(ui)
|
StripBuilder::new(ui)
|
||||||
|
.size(Size::exact(40.0))
|
||||||
.sizes(sizes, timelines)
|
.sizes(sizes, timelines)
|
||||||
.clip(true)
|
.clip(true)
|
||||||
.horizontal(|mut strip| {
|
.horizontal(|mut strip| {
|
||||||
|
strip.cell(|ui| {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if side_panel.response.clicked() {
|
||||||
|
info!("clicked {:?}", side_panel.action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for timeline_ind in 0..timelines {
|
for timeline_ind in 0..timelines {
|
||||||
strip.cell(|ui| timeline::timeline_view(ui, app, timeline_ind));
|
strip.cell(|ui| timeline::timeline_view(ui, app, timeline_ind));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use egui::Color32;
|
use egui::Color32;
|
||||||
|
|
||||||
pub const PURPLE: Color32 = Color32::from_rgb(0xCC, 0x43, 0xC5);
|
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 DARK_BG: Color32 = egui::Color32::from_rgb(40, 44, 52);
|
||||||
pub const GRAY_SECONDARY: Color32 = Color32::from_rgb(0x8A, 0x8A, 0x8A);
|
pub const GRAY_SECONDARY: Color32 = Color32::from_rgb(0x8A, 0x8A, 0x8A);
|
||||||
const BLACK: Color32 = Color32::from_rgb(0x00, 0x00, 0x00);
|
const BLACK: Color32 = Color32::from_rgb(0x00, 0x00, 0x00);
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> {
|
|||||||
} else if let Ok(pubkey) = Pubkey::try_from_hex_str_with_verify(tmp_key) {
|
} else if let Ok(pubkey) = Pubkey::try_from_hex_str_with_verify(tmp_key) {
|
||||||
Ok(Keypair::only_pubkey(pubkey))
|
Ok(Keypair::only_pubkey(pubkey))
|
||||||
} else if let Ok(secret_key) = SecretKey::from_str(tmp_key) {
|
} else if let Ok(secret_key) = SecretKey::from_str(tmp_key) {
|
||||||
Ok(Keypair::new(secret_key))
|
Ok(Keypair::from_secret(secret_key))
|
||||||
} else {
|
} else {
|
||||||
Err(LoginError::InvalidKey)
|
Err(LoginError::InvalidKey)
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ mod tests {
|
|||||||
|
|
||||||
promise_assert!(
|
promise_assert!(
|
||||||
assert_eq,
|
assert_eq,
|
||||||
Ok(Keypair::new(expected_privkey)),
|
Ok(Keypair::from_secret(expected_privkey)),
|
||||||
&login_key_result
|
&login_key_result
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ mod tests {
|
|||||||
|
|
||||||
promise_assert!(
|
promise_assert!(
|
||||||
assert_eq,
|
assert_eq,
|
||||||
Ok(Keypair::new(expected_privkey)),
|
Ok(Keypair::from_secret(expected_privkey)),
|
||||||
&login_key_result
|
&login_key_result
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use enostr::FullKeypair;
|
use enostr::Keypair;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::macos_key_storage::MacOSKeyStorage;
|
use crate::macos_key_storage::MacOSKeyStorage;
|
||||||
@@ -17,15 +17,15 @@ pub enum KeyStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeyStorage {
|
impl KeyStorage {
|
||||||
pub fn get_keys(&self) -> Result<Vec<FullKeypair>, KeyStorageError> {
|
pub fn get_keys(&self) -> Result<Vec<Keypair>, KeyStorageError> {
|
||||||
match self {
|
match self {
|
||||||
Self::None => Ok(Vec::new()),
|
Self::None => Ok(Vec::new()),
|
||||||
#[cfg(target_os = "macos")]
|
#[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;
|
let _ = key;
|
||||||
match self {
|
match self {
|
||||||
Self::None => Ok(()),
|
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;
|
let _ = key;
|
||||||
match self {
|
match self {
|
||||||
Self::None => Ok(()),
|
Self::None => Ok(()),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ mod profile;
|
|||||||
mod relay_generation;
|
mod relay_generation;
|
||||||
pub mod relay_pool_manager;
|
pub mod relay_pool_manager;
|
||||||
mod result;
|
mod result;
|
||||||
|
mod route;
|
||||||
mod test_data;
|
mod test_data;
|
||||||
mod time;
|
mod time;
|
||||||
mod timecache;
|
mod timecache;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#![cfg(target_os = "macos")]
|
#![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::item::{ItemClass, ItemSearchOptions, Limit, SearchResult};
|
||||||
use security_framework::passwords::{delete_generic_password, set_generic_password};
|
use security_framework::passwords::{delete_generic_password, set_generic_password};
|
||||||
@@ -16,11 +16,13 @@ impl<'a> MacOSKeyStorage<'a> {
|
|||||||
MacOSKeyStorage { service_name }
|
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(
|
match set_generic_password(
|
||||||
self.service_name,
|
self.service_name,
|
||||||
key.pubkey.hex().as_str(),
|
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(()),
|
Ok(_) => Ok(()),
|
||||||
Err(_) => Err(KeyStorageError::Addition(key.pubkey.hex())),
|
Err(_) => Err(KeyStorageError::Addition(key.pubkey.hex())),
|
||||||
@@ -82,12 +84,12 @@ impl<'a> MacOSKeyStorage<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_fullkeypairs(&self) -> Vec<FullKeypair> {
|
pub fn get_all_keypairs(&self) -> Vec<Keypair> {
|
||||||
self.get_pubkeys()
|
self.get_pubkeys()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|pubkey| {
|
.map(|pubkey| {
|
||||||
let maybe_secret = self.get_secret_key_for_pubkey(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()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,8 @@ impl<'a> MacOSKeyStorage<'a> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use enostr::FullKeypair;
|
||||||
|
|
||||||
static TEST_SERVICE_NAME: &str = "NOTEDECKTEST";
|
static TEST_SERVICE_NAME: &str = "NOTEDECKTEST";
|
||||||
static STORAGE: MacOSKeyStorage = MacOSKeyStorage {
|
static STORAGE: MacOSKeyStorage = MacOSKeyStorage {
|
||||||
service_name: TEST_SERVICE_NAME,
|
service_name: TEST_SERVICE_NAME,
|
||||||
@@ -119,7 +123,7 @@ mod tests {
|
|||||||
fn add_and_remove_test_pubkey_only() {
|
fn add_and_remove_test_pubkey_only() {
|
||||||
let num_keys_before_test = STORAGE.get_pubkeys().len();
|
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);
|
let add_result = STORAGE.add_key(&keypair);
|
||||||
assert_eq!(add_result, Ok(()));
|
assert_eq!(add_result, Ok(()));
|
||||||
|
|
||||||
@@ -134,18 +138,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_and_remove_full_n(n: usize) {
|
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
|
// there must be zero keys in storage for the test to work as intended
|
||||||
assert_eq!(num_keys_before_test, 0);
|
assert_eq!(num_keys_before_test, 0);
|
||||||
|
|
||||||
let expected_keypairs: Vec<FullKeypair> = (0..n).map(|_| FullKeypair::generate()).collect();
|
let expected_keypairs: Vec<Keypair> = (0..n)
|
||||||
|
.map(|_| FullKeypair::generate().to_keypair())
|
||||||
|
.collect();
|
||||||
|
|
||||||
expected_keypairs.iter().for_each(|keypair| {
|
expected_keypairs.iter().for_each(|keypair| {
|
||||||
let add_result = STORAGE.add_key(keypair);
|
let add_result = STORAGE.add_key(keypair);
|
||||||
assert_eq!(add_result, Ok(()));
|
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);
|
assert_eq!(expected_keypairs, asserted_keypairs);
|
||||||
|
|
||||||
expected_keypairs.iter().for_each(|keypair| {
|
expected_keypairs.iter().for_each(|keypair| {
|
||||||
@@ -153,7 +159,7 @@ mod tests {
|
|||||||
assert_eq!(remove_result, Ok(()));
|
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);
|
assert_eq!(num_keys_after_test, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,8 @@
|
|||||||
use crate::relay_pool_manager::create_wakeup;
|
use enostr::RelayPool;
|
||||||
use enostr::{Pubkey, RelayPool};
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub enum RelayGenerator {
|
fn test_relay_pool(wakeup: impl Fn() + Send + Sync + Clone + 'static) -> RelayPool {
|
||||||
GossipModel,
|
|
||||||
Nip65,
|
|
||||||
Constant,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RelayGenerator {
|
|
||||||
pub fn generate_relays_for(&self, key: &Pubkey, 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: &Pubkey, ctx: &egui::Context) -> RelayPool {
|
|
||||||
let _ = ctx;
|
|
||||||
let _ = key;
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_relays_nip65(key: &Pubkey, ctx: &egui::Context) -> RelayPool {
|
|
||||||
let _ = ctx;
|
|
||||||
let _ = key;
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_constant_relays(ctx: &egui::Context) -> RelayPool {
|
|
||||||
let mut pool = RelayPool::new();
|
let mut pool = RelayPool::new();
|
||||||
let wakeup = create_wakeup(ctx);
|
|
||||||
|
|
||||||
if let Err(e) = pool.add_url("ws://localhost:8080".to_string(), wakeup.clone()) {
|
if let Err(e) = pool.add_url("ws://localhost:8080".to_string(), wakeup.clone()) {
|
||||||
error!("{:?}", e)
|
error!("{:?}", e)
|
||||||
|
|||||||
7
src/route.rs
Normal file
7
src/route.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use nostrdb::NoteKey;
|
||||||
|
|
||||||
|
/// App routing. These describe different places you can go inside Notedeck.
|
||||||
|
pub enum Route {
|
||||||
|
ManageAccount,
|
||||||
|
Thread(NoteKey),
|
||||||
|
}
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
use enostr::RelayPool;
|
use std::path::Path;
|
||||||
use nostrdb::ProfileRecord;
|
|
||||||
|
use enostr::{FullKeypair, Pubkey, RelayPool};
|
||||||
|
use nostrdb::{Config, Ndb, ProfileRecord};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
account_manager::{AccountManager, UserAccount},
|
||||||
|
imgcache::ImageCache,
|
||||||
|
key_storage::KeyStorage,
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
pub fn sample_pool() -> RelayPool {
|
pub fn sample_pool() -> RelayPool {
|
||||||
@@ -54,3 +62,45 @@ const TEST_PROFILE_DATA: [u8; 448] = [
|
|||||||
pub fn test_profile_record() -> ProfileRecord<'static> {
|
pub fn test_profile_record() -> ProfileRecord<'static> {
|
||||||
ProfileRecord::new_owned(&TEST_PROFILE_DATA).unwrap()
|
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<UserAccount> {
|
||||||
|
TEN_ACCOUNT_HEXES
|
||||||
|
.iter()
|
||||||
|
.map(|account_hex| {
|
||||||
|
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);
|
||||||
|
let accounts = get_test_accounts();
|
||||||
|
for account in accounts {
|
||||||
|
account_manager.add_account(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use egui::{
|
use crate::colors::PINK;
|
||||||
Align, Align2, Button, Frame, Id, Layout, Margin, RichText, ScrollArea, Sense, Vec2, Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account_manager::{AccountManager, SimpleProfilePreviewController, UserAccount},
|
account_manager::AccountManager,
|
||||||
app_style::NotedeckTextStyle,
|
app_style::NotedeckTextStyle,
|
||||||
ui::{self, Preview, View},
|
ui::{self, Preview, View},
|
||||||
};
|
};
|
||||||
|
use egui::{Align, Button, Frame, Image, Layout, RichText, ScrollArea, Vec2};
|
||||||
|
|
||||||
|
use super::profile::preview::SimpleProfilePreview;
|
||||||
|
use super::profile::{ProfilePreviewOp, SimpleProfilePreviewController};
|
||||||
|
|
||||||
pub struct AccountManagementView<'a> {
|
pub struct AccountManagementView<'a> {
|
||||||
account_manager: AccountManager<'a>,
|
account_manager: &'a mut AccountManager,
|
||||||
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
||||||
edit_mode: &'a mut bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> View for AccountManagementView<'a> {
|
impl<'a> View for AccountManagementView<'a> {
|
||||||
@@ -26,120 +26,51 @@ impl<'a> View for AccountManagementView<'a> {
|
|||||||
|
|
||||||
impl<'a> AccountManagementView<'a> {
|
impl<'a> AccountManagementView<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
account_manager: AccountManager<'a>,
|
account_manager: &'a mut AccountManager,
|
||||||
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
||||||
edit_mode: &'a mut bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
AccountManagementView {
|
AccountManagementView {
|
||||||
account_manager,
|
account_manager,
|
||||||
simple_preview_controller,
|
simple_preview_controller,
|
||||||
edit_mode,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, ui: &mut egui::Ui) {
|
fn show(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.add_space(24.0);
|
Frame::none().outer_margin(24.0).show(ui, |ui| {
|
||||||
let screen_size = ui.ctx().screen_rect();
|
self.top_section_buttons_widget(ui);
|
||||||
let margin_amt = 128.0;
|
ui.add_space(8.0);
|
||||||
let window_size = Vec2::new(
|
scroll_area().show(ui, |ui| {
|
||||||
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);
|
self.show_accounts(ui);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_accounts(&mut self, ui: &mut egui::Ui) {
|
fn show_accounts(&mut self, ui: &mut egui::Ui) {
|
||||||
scroll_area().show(ui, |ui| {
|
let maybe_remove = self.simple_preview_controller.set_profile_previews(
|
||||||
ui.horizontal_wrapped(|ui| {
|
self.account_manager,
|
||||||
let maybe_remove = self.simple_preview_controller.set_profile_previews(
|
ui,
|
||||||
&self.account_manager,
|
account_card_ui(),
|
||||||
ui,
|
);
|
||||||
*self.edit_mode,
|
|
||||||
|ui, preview, edit_mode| {
|
|
||||||
let mut should_remove = false;
|
|
||||||
|
|
||||||
ui.add_sized(preview.dimensions(), |ui: &mut egui::Ui| {
|
self.maybe_remove_accounts(maybe_remove);
|
||||||
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) {
|
fn show_accounts_mobile(&mut self, ui: &mut egui::Ui) {
|
||||||
scroll_area().show(ui, |ui| {
|
ui.allocate_ui_with_layout(
|
||||||
ui.allocate_ui_with_layout(
|
Vec2::new(ui.available_size_before_wrap().x, 32.0),
|
||||||
Vec2::new(ui.available_size_before_wrap().x, 32.0),
|
Layout::top_down(egui::Align::Min),
|
||||||
Layout::top_down(egui::Align::Min),
|
|ui| {
|
||||||
|ui| {
|
// create all account 'cards' and get the indicies the user requested to remove
|
||||||
let maybe_remove = self.simple_preview_controller.set_profile_previews(
|
let maybe_remove = self.simple_preview_controller.set_profile_previews(
|
||||||
&self.account_manager,
|
self.account_manager,
|
||||||
ui,
|
ui,
|
||||||
*self.edit_mode,
|
account_card_ui(), // closure for creating an account 'card'
|
||||||
|ui, preview, edit_mode| {
|
);
|
||||||
let mut should_remove = false;
|
|
||||||
|
|
||||||
ui.add_sized(
|
// remove all account indicies user requested
|
||||||
Vec2::new(ui.available_width(), 50.0),
|
self.maybe_remove_accounts(maybe_remove);
|
||||||
|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>>) {
|
fn maybe_remove_accounts(&mut self, account_indices: Option<Vec<usize>>) {
|
||||||
@@ -153,68 +84,96 @@ impl<'a> AccountManagementView<'a> {
|
|||||||
fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.show(ui.ctx(), |ui| {
|
.show(ui.ctx(), |ui| {
|
||||||
ui.add(title());
|
mobile_title(ui);
|
||||||
ui.add(self.buttons_widget());
|
self.top_section_buttons_widget(ui);
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
self.show_accounts_mobile(ui);
|
scroll_area().show(ui, |ui| {
|
||||||
|
self.show_accounts_mobile(ui);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buttons_widget(&mut self) -> impl egui::Widget + '_ {
|
fn top_section_buttons_widget(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
|ui: &mut egui::Ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.allocate_ui_with_layout(
|
||||||
ui.allocate_ui_with_layout(
|
Vec2::new(ui.available_size_before_wrap().x, 32.0),
|
||||||
Vec2::new(ui.available_size_before_wrap().x, 32.0),
|
Layout::left_to_right(egui::Align::Center),
|
||||||
Layout::left_to_right(egui::Align::Center),
|
|ui| {
|
||||||
|ui| {
|
if ui.add(add_account_button()).clicked() {
|
||||||
if *self.edit_mode {
|
// TODO: route to AccountLoginView
|
||||||
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(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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
|
.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn account_card_ui() -> fn(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
preview: SimpleProfilePreview,
|
||||||
|
width: f32,
|
||||||
|
is_selected: bool,
|
||||||
|
) -> Option<ProfilePreviewOp> {
|
||||||
|
|ui, preview, width, is_selected| {
|
||||||
|
let mut op: Option<ProfilePreviewOp> = None;
|
||||||
|
|
||||||
|
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);
|
||||||
|
op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
fn scroll_area() -> ScrollArea {
|
||||||
egui::ScrollArea::vertical()
|
egui::ScrollArea::vertical()
|
||||||
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
|
||||||
@@ -222,160 +181,84 @@ fn scroll_area() -> ScrollArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_account_button() -> Button<'static> {
|
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> {
|
fn sign_out_button(ui: &egui::Ui) -> egui::Button<'static> {
|
||||||
Button::new("Edit").min_size(Vec2::new(0.0, 32.0))
|
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> {
|
fn switch_button(dark_mode: bool) -> egui::Button<'static> {
|
||||||
Button::new("Done").min_size(Vec2::new(0.0, 32.0))
|
let _ = dark_mode;
|
||||||
|
|
||||||
|
egui::Button::new("Switch").min_size(Vec2::new(76.0, 32.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_button(_dark_mode: bool) -> egui::Button<'static> {
|
fn selected_widget() -> impl egui::Widget {
|
||||||
let img_data = egui::include_image!("../../assets/icons/delete_icon_4x.png");
|
|ui: &mut egui::Ui| {
|
||||||
|
Frame::none()
|
||||||
egui::Button::image(egui::Image::new(img_data).max_width(30.0)).frame(true)
|
.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");
|
||||||
pub struct AccountSelectionWidget<'a> {
|
let img = Image::new(img_data).max_size(Vec2::new(16.0, 16.0));
|
||||||
account_manager: AccountManager<'a>,
|
ui.add(img);
|
||||||
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
})
|
||||||
}
|
.response
|
||||||
|
|
||||||
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> {
|
// fn logout_all_button() -> egui::Button<'static> {
|
||||||
pub fn new(
|
// egui::Button::new("Logout all")
|
||||||
account_manager: AccountManager<'a>,
|
// }
|
||||||
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
|
||||||
) -> Self {
|
|
||||||
AccountSelectionWidget {
|
|
||||||
account_manager,
|
|
||||||
simple_preview_controller,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PREVIEWS
|
// PREVIEWS
|
||||||
|
|
||||||
mod preview {
|
mod preview {
|
||||||
use enostr::{FullKeypair, Pubkey};
|
use nostrdb::Ndb;
|
||||||
use nostrdb::{Config, Ndb};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::key_storage::KeyStorage;
|
use crate::{imgcache::ImageCache, test_data::get_accmgr_and_ndb_and_imgcache};
|
||||||
use crate::relay_generation::RelayGenerator;
|
|
||||||
use crate::{imgcache::ImageCache, test_data};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
const ACCOUNT_HEXES: [&str; 10] = [
|
|
||||||
"3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681",
|
|
||||||
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
|
|
||||||
"bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91",
|
|
||||||
"5c10ed0678805156d39ef1ef6d46110fe1e7e590ae04986ccf48ba1299cb53e2",
|
|
||||||
"4c96d763eb2fe01910f7e7220b7c7ecdbe1a70057f344b9f79c28af080c3ee30",
|
|
||||||
"edf16b1dd61eab353a83af470cc13557029bff6827b4cb9b7fc9bdb632a2b8e6",
|
|
||||||
"3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681",
|
|
||||||
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
|
|
||||||
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
|
|
||||||
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
|
|
||||||
];
|
|
||||||
|
|
||||||
pub struct AccountManagementPreview {
|
pub struct AccountManagementPreview {
|
||||||
accounts: Vec<UserAccount>,
|
account_manager: AccountManager,
|
||||||
ndb: Ndb,
|
ndb: Ndb,
|
||||||
img_cache: ImageCache,
|
img_cache: ImageCache,
|
||||||
edit_mode: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_accounts() -> Vec<UserAccount> {
|
|
||||||
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);
|
|
||||||
|
|
||||||
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 {
|
impl AccountManagementPreview {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let accounts = get_accounts();
|
let (account_manager, ndb, img_cache) = get_accmgr_and_ndb_and_imgcache();
|
||||||
let (ndb, img_cache) = get_ndb_and_img_cache();
|
|
||||||
|
|
||||||
AccountManagementPreview {
|
AccountManagementPreview {
|
||||||
accounts,
|
account_manager,
|
||||||
ndb,
|
ndb,
|
||||||
img_cache,
|
img_cache,
|
||||||
edit_mode: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for AccountManagementPreview {
|
impl View for AccountManagementPreview {
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
let account_manager = AccountManager::new(
|
ui.add_space(24.0);
|
||||||
&mut self.accounts,
|
|
||||||
KeyStorage::None,
|
|
||||||
RelayGenerator::Constant,
|
|
||||||
);
|
|
||||||
|
|
||||||
AccountManagementView::new(
|
AccountManagementView::new(
|
||||||
account_manager,
|
&mut self.account_manager,
|
||||||
SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache),
|
SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache),
|
||||||
&mut self.edit_mode,
|
|
||||||
)
|
)
|
||||||
.ui(ui);
|
.ui(ui);
|
||||||
}
|
}
|
||||||
@@ -388,49 +271,4 @@ mod preview {
|
|||||||
AccountManagementPreview::new()
|
AccountManagementPreview::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AccountSelectionPreview {
|
|
||||||
accounts: Vec<UserAccount>,
|
|
||||||
ndb: Ndb,
|
|
||||||
img_cache: ImageCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AccountSelectionPreview {
|
|
||||||
fn new() -> Self {
|
|
||||||
let accounts = get_accounts();
|
|
||||||
let (ndb, img_cache) = get_ndb_and_img_cache();
|
|
||||||
AccountSelectionPreview {
|
|
||||||
accounts,
|
|
||||||
ndb,
|
|
||||||
img_cache,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(account) = widget.ui(ui) {
|
|
||||||
println!("User made selection: {:?}", account.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Preview for AccountSelectionWidget<'a> {
|
|
||||||
type Prev = AccountSelectionPreview;
|
|
||||||
|
|
||||||
fn preview() -> Self::Prev {
|
|
||||||
AccountSelectionPreview::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
260
src/ui/account_switcher.rs
Normal file
260
src/ui/account_switcher.rs
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
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 super::profile::{preview::SimpleProfilePreview, SimpleProfilePreviewController};
|
||||||
|
|
||||||
|
pub struct AccountSelectionWidget<'a> {
|
||||||
|
account_manager: &'a AccountManager,
|
||||||
|
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AccountSelectAction {
|
||||||
|
RemoveAccount { index: usize },
|
||||||
|
SelectAccount { index: usize },
|
||||||
|
OpenAccountManagement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AccountSelectResponse {
|
||||||
|
action: Option<AccountSelectAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
self.show(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| {
|
||||||
|
res = top_section_widget(ui);
|
||||||
|
|
||||||
|
scroll_area().show(ui, |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(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: &UserAccount) -> bool {
|
||||||
|
if let Ok(response) = self.sign_out_button(ui, account) {
|
||||||
|
response.clicked()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
|
let _ = ui;
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_accounts(&mut self, ui: &mut egui::Ui) -> Option<usize> {
|
||||||
|
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) -> Result<egui::Response> {
|
||||||
|
self.simple_preview_controller.show_with_nickname(
|
||||||
|
ui,
|
||||||
|
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(
|
||||||
|
img,
|
||||||
|
RichText::new(format!(" Sign out @{}", username.username()))
|
||||||
|
.color(PINK)
|
||||||
|
.size(16.0),
|
||||||
|
)
|
||||||
|
.frame(false);
|
||||||
|
|
||||||
|
ui.add(button)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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::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> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
&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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
pub mod account_login_view;
|
pub mod account_login_view;
|
||||||
pub mod account_management;
|
pub mod account_management;
|
||||||
|
pub mod account_switcher;
|
||||||
pub mod anim;
|
pub mod anim;
|
||||||
pub mod mention;
|
pub mod mention;
|
||||||
pub mod note;
|
pub mod note;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod relay;
|
pub mod relay;
|
||||||
|
pub mod side_panel;
|
||||||
pub mod username;
|
pub mod username;
|
||||||
|
|
||||||
pub use account_management::{AccountManagementView, AccountSelectionWidget};
|
pub use account_management::AccountManagementView;
|
||||||
|
pub use account_switcher::AccountSelectionWidget;
|
||||||
pub use mention::Mention;
|
pub use mention::Mention;
|
||||||
pub use note::Note;
|
pub use note::Note;
|
||||||
pub use preview::{Preview, PreviewApp};
|
pub use preview::{Preview, PreviewApp};
|
||||||
pub use profile::{ProfilePic, ProfilePreview};
|
pub use profile::{ProfilePic, ProfilePreview};
|
||||||
pub use relay::RelayView;
|
pub use relay::RelayView;
|
||||||
|
pub use side_panel::DesktopSidePanel;
|
||||||
pub use username::Username;
|
pub use username::Username;
|
||||||
|
|
||||||
use egui::Margin;
|
use egui::Margin;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
pub mod picture;
|
pub mod picture;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
|
mod profile_preview_controller;
|
||||||
|
|
||||||
pub use picture::ProfilePic;
|
pub use picture::ProfilePic;
|
||||||
pub use preview::ProfilePreview;
|
pub use preview::ProfilePreview;
|
||||||
|
pub use profile_preview_controller::{ProfilePreviewOp, SimpleProfilePreviewController};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::imgcache::ImageCache;
|
|||||||
use crate::ui::ProfilePic;
|
use crate::ui::ProfilePic;
|
||||||
use crate::{colors, images, DisplayName};
|
use crate::{colors, images, DisplayName};
|
||||||
use egui::load::TexturePoll;
|
use egui::load::TexturePoll;
|
||||||
use egui::{Frame, RichText, Sense, Vec2, Widget};
|
use egui::{Frame, RichText, Sense, Widget};
|
||||||
use egui_extras::Size;
|
use egui_extras::Size;
|
||||||
use nostrdb::ProfileRecord;
|
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 {
|
pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut ImageCache) -> Self {
|
||||||
SimpleProfilePreview { profile, cache }
|
SimpleProfilePreview { profile, cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dimensions(&self) -> Vec2 {
|
|
||||||
Vec2::new(120.0, 150.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'cache> egui::Widget for SimpleProfilePreview<'a, 'cache> {
|
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) {
|
if let Some(name) = crate::profile::get_profile_name(profile) {
|
||||||
name
|
name
|
||||||
} else {
|
} else {
|
||||||
@@ -160,7 +156,7 @@ 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()) {
|
if let Some(url) = profile.record().profile().and_then(|p| p.picture()) {
|
||||||
url
|
url
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
168
src/ui/profile/profile_preview_controller.rs
Normal file
168
src/ui/profile/profile_preview_controller.rs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
use nostrdb::{Ndb, Transaction};
|
||||||
|
|
||||||
|
use crate::{account_manager::AccountManager, imgcache::ImageCache, DisplayName, Result};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
preview::{get_display_name, get_profile_url, SimpleProfilePreview},
|
||||||
|
ProfilePic,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct SimpleProfilePreviewController<'a> {
|
||||||
|
ndb: &'a Ndb,
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_profile_previews(
|
||||||
|
&mut self,
|
||||||
|
account_manager: &mut AccountManager,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
add_preview_ui: fn(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
preview: SimpleProfilePreview,
|
||||||
|
width: f32,
|
||||||
|
is_selected: bool,
|
||||||
|
) -> Option<ProfilePreviewOp>,
|
||||||
|
) -> Option<Vec<usize>> {
|
||||||
|
let mut to_remove: Option<Vec<usize>> = None;
|
||||||
|
|
||||||
|
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() {
|
||||||
|
let account = if let Some(account) = account_manager.get_account(i) {
|
||||||
|
account
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile =
|
||||||
|
if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, account.pubkey.bytes()) {
|
||||||
|
profile
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let preview = SimpleProfilePreview::new(&profile, self.img_cache);
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to_remove
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_profile_previews(
|
||||||
|
&mut self,
|
||||||
|
account_manager: &AccountManager,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
add_preview_ui: fn(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
preview: SimpleProfilePreview,
|
||||||
|
width: f32,
|
||||||
|
is_selected: bool,
|
||||||
|
index: usize,
|
||||||
|
) -> bool,
|
||||||
|
) -> Option<usize> {
|
||||||
|
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() {
|
||||||
|
let account = if let Some(account) = account_manager.get_account(i) {
|
||||||
|
account
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile =
|
||||||
|
if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, account.pubkey.bytes()) {
|
||||||
|
profile
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let preview = SimpleProfilePreview::new(&profile, self.img_cache);
|
||||||
|
|
||||||
|
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: &[u8; 32],
|
||||||
|
ui_element: fn(ui: &mut egui::Ui, username: &DisplayName) -> egui::Response,
|
||||||
|
) -> Result<egui::Response> {
|
||||||
|
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(
|
||||||
|
self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
key: &[u8; 32],
|
||||||
|
ui_element: fn(ui: &mut egui::Ui, pfp: ProfilePic) -> egui::Response,
|
||||||
|
) -> Option<egui::Response> {
|
||||||
|
if let Ok(txn) = Transaction::new(self.ndb) {
|
||||||
|
let profile = self.ndb.get_profile_by_pubkey(&txn, key);
|
||||||
|
|
||||||
|
if let Ok(profile) = profile {
|
||||||
|
return Some(ui_element(
|
||||||
|
ui,
|
||||||
|
ProfilePic::new(self.img_cache, get_profile_url(&profile)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
172
src/ui/side_panel.rs
Normal file
172
src/ui/side_panel.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
use egui::{Button, Layout, SidePanel, Vec2, Widget};
|
||||||
|
|
||||||
|
use crate::account_manager::AccountManager;
|
||||||
|
|
||||||
|
use super::{profile::SimpleProfilePreviewController, ProfilePic, View};
|
||||||
|
|
||||||
|
pub struct DesktopSidePanel<'a> {
|
||||||
|
selected_account: Option<&'a [u8; 32]>,
|
||||||
|
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).response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DesktopSidePanel<'a> {
|
||||||
|
pub fn new(
|
||||||
|
selected_account: Option<&'a [u8; 32]>,
|
||||||
|
simple_preview_controller: SimpleProfilePreviewController<'a>,
|
||||||
|
) -> Self {
|
||||||
|
DesktopSidePanel {
|
||||||
|
selected_account,
|
||||||
|
simple_preview_controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn panel() -> SidePanel {
|
||||||
|
egui::SidePanel::left("side_panel")
|
||||||
|
.resizable(false)
|
||||||
|
.exact_width(40.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(self, ui: &mut egui::Ui) -> SidePanelResponse {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod preview {
|
||||||
|
use nostrdb::Ndb;
|
||||||
|
|
||||||
|
use crate::{imgcache::ImageCache, test_data, ui::Preview};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct DesktopSidePanelPreview {
|
||||||
|
account_manager: AccountManager,
|
||||||
|
ndb: Ndb,
|
||||||
|
img_cache: ImageCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopSidePanelPreview {
|
||||||
|
fn new() -> Self {
|
||||||
|
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 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Preview for DesktopSidePanel<'a> {
|
||||||
|
type Prev = DesktopSidePanelPreview;
|
||||||
|
|
||||||
|
fn preview() -> Self::Prev {
|
||||||
|
DesktopSidePanelPreview::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ use notedeck::app_creation::{
|
|||||||
};
|
};
|
||||||
use notedeck::ui::account_login_view::AccountLoginView;
|
use notedeck::ui::account_login_view::AccountLoginView;
|
||||||
use notedeck::ui::{
|
use notedeck::ui::{
|
||||||
AccountManagementView, AccountSelectionWidget, Preview, PreviewApp, ProfilePic, ProfilePreview,
|
AccountManagementView, AccountSelectionWidget, DesktopSidePanel, Preview, PreviewApp,
|
||||||
RelayView,
|
ProfilePic, ProfilePreview, RelayView,
|
||||||
};
|
};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
@@ -88,5 +88,6 @@ async fn main() {
|
|||||||
ProfilePic,
|
ProfilePic,
|
||||||
AccountManagementView,
|
AccountManagementView,
|
||||||
AccountSelectionWidget,
|
AccountSelectionWidget,
|
||||||
|
DesktopSidePanel,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use enostr::{FullKeypair, RelayPool};
|
use enostr::Keypair;
|
||||||
|
|
||||||
pub struct UserAccount {
|
//pub struct UserAccount {
|
||||||
pub key: FullKeypair,
|
//pub key: Keypair,
|
||||||
pub relays: RelayPool,
|
//pub relays: RelayPool,
|
||||||
}
|
//pub relays: Vec<String>,
|
||||||
|
//}
|
||||||
|
|
||||||
|
pub type UserAccount = Keypair;
|
||||||
|
|||||||
Reference in New Issue
Block a user