notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 0dd33c90e7664d934f0530cf8499d6bc193c436a
parent bff0f3f628ce31ee141a7f71ea546b76dba38fed
Author: William Casarin <jb55@jb55.com>
Date:   Tue,  4 Jun 2024 01:51:30 -0500

initial navigation

Diffstat:
MCargo.lock | 42+++++++++++++++++++++---------------------
MCargo.toml | 17+++++++++++++++--
Menostr/Cargo.toml | 1-
Menostr/src/lib.rs | 2++
Menostr/src/note.rs | 91+++++++------------------------------------------------------------------------
Msrc/app.rs | 103++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/route.rs | 23+++++++++++++++++++----
Msrc/test_data.rs | 27+++++++++------------------
Msrc/timeline.rs | 26++++++++++++++++++++++----
Msrc/ui/account_management.rs | 112+++++++++++++++++++++++++------------------------------------------------------
Msrc/ui/account_switcher.rs | 155++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/ui/mod.rs | 6+++---
Msrc/ui/note/mod.rs | 193+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/ui/profile/mod.rs | 4++--
Msrc/ui/profile/profile_preview_controller.rs | 229+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/ui/side_panel.rs | 75+++++++++++++++++++++++++++++++--------------------------------------------
16 files changed, 529 insertions(+), 577 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1036,15 +1036,6 @@ dependencies = [ ] [[package]] -name = "egui-tabs" -version = "0.1.0" -source = "git+https://github.com/damus-io/egui-tabs?rev=75f47141aebcf876986fad00dd83a69a7bb04840#75f47141aebcf876986fad00dd83a69a7bb04840" -dependencies = [ - "egui", - "egui_extras", -] - -[[package]] name = "egui-wgpu" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1111,6 +1102,25 @@ dependencies = [ "puffin", "wasm-bindgen", "web-sys", + "winit", +] + +[[package]] +name = "egui_nav" +version = "0.1.0" +source = "git+https://github.com/damus-io/egui-nav?rev=9f640df83494a79cd7aa0b5983c83290d284d6ee#9f640df83494a79cd7aa0b5983c83290d284d6ee" +dependencies = [ + "egui", + "egui_extras", +] + +[[package]] +name = "egui_tabs" +version = "0.1.0" +source = "git+https://github.com/damus-io/egui-tabs?rev=120971fc43db6ba0b6f194f4bd4a66f7e00a4e22#120971fc43db6ba0b6f194f4bd4a66f7e00a4e22" +dependencies = [ + "egui", + "egui_extras", ] [[package]] @@ -1178,7 +1188,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "shatter", "tracing", ] @@ -2454,8 +2463,9 @@ dependencies = [ "console_error_panic_hook", "eframe", "egui", - "egui-tabs", "egui_extras", + "egui_nav", + "egui_tabs", "egui_virtual_list", "ehttp 0.2.0", "enostr", @@ -3525,16 +3535,6 @@ dependencies = [ ] [[package]] -name = "shatter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c72d316f805789a3a026fd327dd854ece0fbdc5fb4f57ac9d1319f3670a4177" -dependencies = [ - "env_logger 0.10.2", - "log", -] - -[[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -14,12 +14,25 @@ crate-type = ["lib", "cdylib"] [dependencies] #egui-android = { git = "https://github.com/jb55/egui-android.git" } egui = "0.27.2" -eframe = { version = "0.27.2", default-features = false, features = [ "glow", "wgpu", "android-native-activity" ] } +eframe = { version = "0.27.2", default-features = false, features = [ "glow", "wgpu", "x11", "wayland", "android-native-activity" ] } + + # + # TODO default features: + # + #"accesskit", + #"default_fonts", + #"glow", + #"wayland", + #"web_screen_reader", + #"winit/default", + #"x11", + #eframe = { version = "0.27.2", default-features = false, features = [ "glow", "android-native-activity" ] } #eframe = "0.22.0" egui_extras = { version = "0.27.2", features = ["all_loaders"] } ehttp = "0.2.0" -egui-tabs = { git = "https://github.com/damus-io/egui-tabs", rev = "75f47141aebcf876986fad00dd83a69a7bb04840" } +egui_tabs = { git = "https://github.com/damus-io/egui-tabs", rev = "120971fc43db6ba0b6f194f4bd4a66f7e00a4e22" } +egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "9f640df83494a79cd7aa0b5983c83290d284d6ee" } reqwest = { version = "0.12.4", default-features = false, features = [ "rustls-tls-native-roots" ] } image = { version = "0.24", features = ["jpeg", "png", "webp"] } log = "0.4.17" diff --git a/enostr/Cargo.toml b/enostr/Cargo.toml @@ -11,7 +11,6 @@ serde_derive = "1" serde = { version = "1", features = ["derive"] } # You only need this if you want app persistence serde_json = "1.0.89" tracing = "0.1.37" -shatter = "0.1.1" nostr = { version = "0.30.0" } hex = "0.4.3" log = "0.4.20" diff --git a/enostr/src/lib.rs b/enostr/src/lib.rs @@ -3,6 +3,7 @@ mod error; mod event; mod filter; mod keypair; +mod note; mod profile; mod pubkey; mod relay; @@ -14,6 +15,7 @@ pub use ewebsock; pub use filter::Filter; pub use keypair::{FullKeypair, Keypair}; pub use nostr::SecretKey; +pub use note::NoteId; pub use profile::Profile; pub use pubkey::Pubkey; pub use relay::message::{RelayEvent, RelayMessage}; diff --git a/enostr/src/note.rs b/enostr/src/note.rs @@ -1,87 +1,12 @@ -use crate::Event; -use shatter::shard::Shards; +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct NoteId([u8; 32]); -#[derive(Debug, Eq, PartialEq)] -struct RefId(i32); - -struct Ref<'a> { - ref_tag: u8, - relay_id: Option<&'a str>, - id: &'a str, -} - -impl<'a> RefId { - fn get_ref(self, tags: &'a Vec<Vec<String>>) -> Option<Ref<'a>> { - let ind = self.0 as usize; - - if ind > tags.len() - 1 { - return None; - } - - let tag = &tags[ind]; - - if tag.len() < 2 { - return None; - } - - if tag[0].len() != 1 { - return None; - } - - let ref_tag = if let Some(rtag) = tag[0].as_bytes().first() { - *rtag - } else { - 0 - }; - - let id = &tag[1]; - if id.len() != 64 { - return None; - } - - let relay_id = if tag[2].len() == 0 { - None - } else { - Some(&*tag[2]) - }; - - Some(Ref { - ref_tag, - relay_id, - id, - }) +impl NoteId { + pub fn new(bytes: [u8; 32]) -> Self { + NoteId(bytes) } -} -enum MentionType { - Pubkey, - Event, -} - -struct Mention { - index: Option<i32>, - typ: MentionType, - refid: RefId, -} - -enum EventRef { - Mention(Mention), - ThreadId(RefId), - Reply(RefId), - ReplyToRoot(RefId), -} - -struct EventRefs { - refs: Vec<EventRef>, -} - -struct DM { - decrypted: Option<String>, - shards: Shards, -} - -struct Note { - event: NostrEvent, - shards: Shards, - refs: EventRef, + pub fn bytes(&self) -> &[u8; 32] { + &self.0 + } } diff --git a/src/app.rs b/src/app.rs @@ -5,13 +5,17 @@ use crate::error::Error; use crate::frame_history::FrameHistory; use crate::imgcache::ImageCache; use crate::notecache::{CachedNote, NoteCache}; +use crate::relay_pool_manager::RelayPoolManager; use crate::route::Route; use crate::timeline; use crate::timeline::{MergeKind, NoteRef, Timeline, ViewFilter}; use crate::ui; -use crate::ui::profile::SimpleProfilePreviewController; -use crate::ui::DesktopSidePanel; +use crate::ui::{DesktopSidePanel, RelayView, SidePanelAction, View}; use crate::Result; +use egui_nav::{Nav, NavAction}; +use enostr::RelayPool; +use std::cell::RefCell; +use std::rc::Rc; use egui::{Context, Frame, Style}; use egui_extras::{Size, StripBuilder}; @@ -25,8 +29,6 @@ use std::path::Path; use std::time::Duration; use tracing::{debug, error, info, warn}; -use enostr::RelayPool; - #[derive(Debug, Eq, PartialEq, Clone)] pub enum DamusState { Initializing, @@ -42,7 +44,7 @@ pub struct Damus { is_mobile: bool, /// global navigation for account management popups, etc. - _nav: Vec<Route>, + //nav: Vec<Route>, pub textmode: bool, pub timelines: Vec<Timeline>, @@ -354,7 +356,9 @@ fn poll_notes_for_timeline<'a>( }; let new_note_ids = damus.ndb.poll_for_notes(sub, 100); - if !new_note_ids.is_empty() { + if new_note_ids.is_empty() { + return Ok(()); + } else { debug!("{} new notes! {:?}", new_note_ids.len(), new_note_ids); } @@ -416,6 +420,10 @@ fn insert_notes_into_timeline( let timeline = &mut app.timelines[timeline_ind]; let num_prev_items = timeline.notes(filter).len(); let (notes, merge_kind) = timeline::merge_sorted_vecs(timeline.notes(filter), new_refs); + debug!( + "got merge kind {:?} for {:?} on timeline {}", + merge_kind, filter, timeline_ind + ); timeline.view_mut(filter).notes = notes; let new_items = timeline.notes(filter).len() - num_prev_items; @@ -697,7 +705,6 @@ impl Damus { img_cache: ImageCache::new(imgcache_dir), note_cache: NoteCache::default(), selected_timeline: 0, - _nav: Vec::with_capacity(6), timelines, textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), @@ -730,7 +737,6 @@ impl Damus { note_cache: NoteCache::default(), selected_timeline: 0, timelines, - _nav: vec![], textmode: false, ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), account_manager: AccountManager::new(None, crate::key_storage::KeyStorage::None), @@ -862,14 +868,71 @@ fn render_panel(ctx: &egui::Context, app: &mut Damus, timeline_ind: usize) { }); } +fn render_nav(routes: Vec<Route>, timeline_ind: usize, app: &mut Damus, ui: &mut egui::Ui) { + let app_ctx = Rc::new(RefCell::new(app)); + + let nav_response = Nav::new(routes).show(ui, |ui, nav| match nav.top() { + Route::Timeline(_n) => { + timeline::timeline_view(ui, &mut app_ctx.borrow_mut(), timeline_ind); + } + + Route::ManageAccount => { + ui.label("account management view"); + } + + Route::Thread(_key) => { + ui.label("thread view"); + } + + Route::Relays => { + let pool = &mut app_ctx.borrow_mut().pool; + let manager = RelayPoolManager::new(pool); + RelayView::new(manager).ui(ui); + } + + Route::Reply(id) => { + let app = app_ctx.borrow(); + let txn = if let Ok(txn) = Transaction::new(&app.ndb) { + txn + } else { + ui.label("Reply to unknown note"); + return; + }; + + let note = if let Ok(note) = app.ndb.get_note_by_id(&txn, id.bytes()) { + note + } else { + ui.label("Reply to unknown note"); + return; + }; + + ui.label(format!( + "Replying to note by {}", + app.ndb + .get_profile_by_pubkey(&txn, note.pubkey()) + .as_ref() + .ok() + .and_then(|pr| Some(crate::profile::get_profile_name(pr)?.username())) + .unwrap_or("??") + )); + } + }); + + if let Some(NavAction::Returned) = nav_response.action { + app_ctx.borrow_mut().timelines[timeline_ind].routes.pop(); + } +} + fn render_damus_mobile(ctx: &egui::Context, app: &mut Damus) { //render_panel(ctx, app, 0); #[cfg(feature = "profiling")] puffin::profile_function!(); + //let routes = app.timelines[0].routes.clone(); + main_panel(&ctx.style(), app.is_mobile()).show(ctx, |ui| { - timeline::timeline_view(ui, app, 0); + render_nav(app.timelines[0].routes.clone(), 0, app, ui); }); } @@ -921,21 +984,27 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, timelines: us .clip(true) .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); + let side_panel = DesktopSidePanel::new(app).show(ui); if side_panel.response.clicked() { info!("clicked {:?}", side_panel.action); + if let SidePanelAction::Account = side_panel.action { + app.timelines[0].routes.push(Route::ManageAccount); + } } }); for timeline_ind in 0..timelines { - strip.cell(|ui| timeline::timeline_view(ui, app, timeline_ind)); + strip.cell(|ui| { + render_nav( + app.timelines[timeline_ind].routes.clone(), + timeline_ind, + app, + ui, + ); + }); + + //strip.cell(|ui| timeline::timeline_view(ui, app, timeline_ind)); } }); } diff --git a/src/route.rs b/src/route.rs @@ -1,9 +1,24 @@ -//use nostrdb::NoteKey; +use enostr::NoteId; +use std::fmt; /// App routing. These describe different places you can go inside Notedeck. +#[derive(Clone, Debug)] pub enum Route { - /* + Timeline(String), ManageAccount, - Thread(NoteKey), - */ + Thread(NoteId), + Reply(NoteId), + Relays, +} + +impl fmt::Display for Route { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Route::ManageAccount => write!(f, "Manage Account"), + Route::Timeline(name) => write!(f, "{}", name), + Route::Thread(_id) => write!(f, "Thread"), + Route::Reply(_id) => write!(f, "Reply"), + Route::Relays => write!(f, "Relays"), + } + } } diff --git a/src/test_data.rs b/src/test_data.rs @@ -1,13 +1,9 @@ use std::path::Path; use enostr::{FullKeypair, Pubkey, RelayPool}; -use nostrdb::{Config, Ndb, ProfileRecord}; +use nostrdb::ProfileRecord; -use crate::{ - account_manager::{AccountManager, UserAccount}, - imgcache::ImageCache, - key_storage::KeyStorage, -}; +use crate::{account_manager::UserAccount, Damus}; #[allow(unused_must_use)] pub fn sample_pool() -> RelayPool { @@ -87,20 +83,15 @@ pub fn get_test_accounts() -> Vec<UserAccount> { .collect() } -pub fn get_accmgr_and_ndb_and_imgcache() -> (AccountManager, Ndb, ImageCache) { - let mut account_manager = AccountManager::new(None, KeyStorage::None); +pub fn get_account_manager_test_app(is_mobile: bool) -> Damus { + let db_dir = Path::new("."); + let path = db_dir.to_str().unwrap(); + let mut app = Damus::mock(path, is_mobile); + let accounts = get_test_accounts(); for account in accounts { - account_manager.add_account(account); + app.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) + app } diff --git a/src/timeline.rs b/src/timeline.rs @@ -1,11 +1,14 @@ use crate::notecache::CachedNote; use crate::{ui, Damus}; +use crate::route::Route; use egui::containers::scroll_area::ScrollBarVisibility; use egui::{Direction, Layout}; + +use crate::ui::BarAction; use egui_tabs::TabColor; use egui_virtual_list::VirtualList; -use enostr::Filter; +use enostr::{Filter, NoteId}; use nostrdb::{Note, NoteKey, Subscription, Transaction}; use std::cell::RefCell; use std::cmp::Ordering; @@ -35,7 +38,7 @@ impl PartialOrd for NoteRef { } } -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum ViewFilter { Notes, NotesAndReplies, @@ -122,6 +125,7 @@ pub struct Timeline { pub filter: Vec<Filter>, pub views: Vec<TimelineView>, pub selected_view: i32, + pub routes: Vec<Route>, /// Our nostrdb subscription pub subscription: Option<Subscription>, @@ -134,12 +138,14 @@ impl Timeline { let replies = TimelineView::new(ViewFilter::NotesAndReplies); let views = vec![notes, replies]; let selected_view = 0; + let routes = vec![Route::Timeline("Timeline".to_string())]; Timeline { filter, views, subscription, selected_view, + routes, } } @@ -233,7 +239,7 @@ fn tabs_ui(timeline: &mut Timeline, ui: &mut egui::Ui) { let stroke = egui::Stroke { color: ui.visuals().hyperlink_color, - width: 3.0, + width: 2.0, }; let speed = 0.1f32; @@ -298,8 +304,19 @@ pub fn timeline_view(ui: &mut egui::Ui, app: &mut Damus, timeline: usize) { ui::padding(8.0, ui, |ui| { let textmode = app.textmode; - ui.add(ui::Note::new(app, &note).note_previews(!textmode)); + let resp = ui::Note::new(app, &note).note_previews(!textmode).show(ui); + if let Some(action) = resp.action { + debug!("bar action: {:?}", action); + match action { + BarAction::Reply => { + app.timelines[timeline] + .routes + .push(Route::Reply(NoteId::new(note.id().to_owned()))); + } + } + } }); + ui::hline(ui); //ui.add(egui::Separator::default().spacing(0.0)); @@ -308,6 +325,7 @@ pub fn timeline_view(ui: &mut egui::Ui, app: &mut Damus, timeline: usize) { }); } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum MergeKind { FrontInsert, Spliced, diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs @@ -2,103 +2,69 @@ use crate::colors::PINK; use crate::{ account_manager::AccountManager, app_style::NotedeckTextStyle, - ui::{Preview, PreviewConfig, View}, + ui::{profile_preview_controller, Preview, PreviewConfig, View}, + Damus, }; use egui::{Align, Button, Frame, Image, Layout, RichText, ScrollArea, Vec2}; use super::profile::preview::SimpleProfilePreview; -use super::profile::{ProfilePreviewOp, SimpleProfilePreviewController}; +use super::profile::ProfilePreviewOp; -pub struct AccountManagementView<'a> { - mobile: bool, - account_manager: &'a mut AccountManager, - simple_preview_controller: SimpleProfilePreviewController<'a>, -} +pub struct AccountManagementView {} -impl<'a> View for AccountManagementView<'a> { - fn ui(&mut self, ui: &mut egui::Ui) { - if self.mobile { - self.show_mobile(ui); - } else { - self.show(ui); - } - } -} - -impl<'a> AccountManagementView<'a> { - pub fn new( - mobile: bool, - account_manager: &'a mut AccountManager, - simple_preview_controller: SimpleProfilePreviewController<'a>, - ) -> Self { - AccountManagementView { - mobile, - account_manager, - simple_preview_controller, - } - } - - fn show(&mut self, ui: &mut egui::Ui) { +impl AccountManagementView { + fn show(app: &mut Damus, ui: &mut egui::Ui) { Frame::none().outer_margin(24.0).show(ui, |ui| { - self.top_section_buttons_widget(ui); + Self::top_section_buttons_widget(ui); ui.add_space(8.0); scroll_area().show(ui, |ui| { - self.show_accounts(ui); + Self::show_accounts(app, ui); }); }); } - fn show_accounts(&mut self, ui: &mut egui::Ui) { - let maybe_remove = self.simple_preview_controller.set_profile_previews( - self.account_manager, - ui, - account_card_ui(), - ); + fn show_accounts(app: &mut Damus, ui: &mut egui::Ui) { + let maybe_remove = + profile_preview_controller::set_profile_previews(app, ui, account_card_ui()); - self.maybe_remove_accounts(maybe_remove); + Self::maybe_remove_accounts(&mut app.account_manager, maybe_remove); } - fn show_accounts_mobile(&mut self, ui: &mut egui::Ui) { + fn show_accounts_mobile(app: &mut Damus, ui: &mut egui::Ui) { ui.allocate_ui_with_layout( Vec2::new(ui.available_size_before_wrap().x, 32.0), Layout::top_down(egui::Align::Min), |ui| { // create all account 'cards' and get the indicies the user requested to remove - let maybe_remove = self.simple_preview_controller.set_profile_previews( - self.account_manager, + let maybe_remove = profile_preview_controller::set_profile_previews( + app, ui, account_card_ui(), // closure for creating an account 'card' ); // remove all account indicies user requested - self.maybe_remove_accounts(maybe_remove); + Self::maybe_remove_accounts(&mut app.account_manager, maybe_remove); }, ); } - fn maybe_remove_accounts(&mut self, account_indices: Option<Vec<usize>>) { + fn maybe_remove_accounts(manager: &mut AccountManager, account_indices: Option<Vec<usize>>) { if let Some(to_remove) = account_indices { to_remove .iter() - .for_each(|index| self.account_manager.remove_account(*index)); + .for_each(|index| manager.remove_account(*index)); } } - fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response { - egui::CentralPanel::default() - .show(ui.ctx(), |ui| { - mobile_title(ui); - self.top_section_buttons_widget(ui); + fn show_mobile(app: &mut Damus, ui: &mut egui::Ui) { + mobile_title(ui); + Self::top_section_buttons_widget(ui); - ui.add_space(8.0); - scroll_area().show(ui, |ui| { - self.show_accounts_mobile(ui); - }); - }) - .response + ui.add_space(8.0); + scroll_area().show(ui, |ui| Self::show_accounts_mobile(app, ui)); } - fn top_section_buttons_widget(&mut self, ui: &mut egui::Ui) -> egui::Response { + fn top_section_buttons_widget(ui: &mut egui::Ui) -> egui::Response { ui.horizontal(|ui| { ui.allocate_ui_with_layout( Vec2::new(ui.available_size_before_wrap().x, 32.0), @@ -233,44 +199,36 @@ fn selected_widget() -> impl egui::Widget { // PREVIEWS mod preview { - use nostrdb::Ndb; + use super::*; - use crate::{imgcache::ImageCache, test_data::get_accmgr_and_ndb_and_imgcache}; + use crate::test_data::get_account_manager_test_app; pub struct AccountManagementPreview { is_mobile: bool, - account_manager: AccountManager, - ndb: Ndb, - img_cache: ImageCache, + app: Damus, } impl AccountManagementPreview { fn new(is_mobile: bool) -> Self { - let (account_manager, ndb, img_cache) = get_accmgr_and_ndb_and_imgcache(); + let app = get_account_manager_test_app(is_mobile); - AccountManagementPreview { - is_mobile, - account_manager, - ndb, - img_cache, - } + AccountManagementPreview { is_mobile, app } } } impl View for AccountManagementPreview { fn ui(&mut self, ui: &mut egui::Ui) { ui.add_space(24.0); - AccountManagementView::new( - self.is_mobile, - &mut self.account_manager, - SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), - ) - .ui(ui); + if self.is_mobile { + AccountManagementView::show_mobile(&mut self.app, ui); + } else { + AccountManagementView::show(&mut self.app, ui); + } } } - impl<'a> Preview for AccountManagementView<'a> { + impl Preview for AccountManagementView { type Prev = AccountManagementPreview; fn preview(cfg: PreviewConfig) -> Self::Prev { diff --git a/src/ui/account_switcher.rs b/src/ui/account_switcher.rs @@ -1,21 +1,18 @@ use crate::{ - account_manager::{AccountManager, UserAccount}, - colors::PINK, - profile::DisplayName, - Result, + account_manager::UserAccount, colors::PINK, profile::DisplayName, + ui::profile_preview_controller, Damus, Result, }; + +use nostrdb::Ndb; + use egui::{ Align, Button, Color32, Frame, Id, Image, Layout, Margin, RichText, Rounding, ScrollArea, Sense, Vec2, }; -use super::profile::{preview::SimpleProfilePreview, SimpleProfilePreviewController}; +use super::profile::preview::SimpleProfilePreview; -pub struct AccountSelectionWidget<'a> { - is_mobile: bool, - account_manager: &'a AccountManager, - simple_preview_controller: SimpleProfilePreviewController<'a>, -} +pub struct AccountSelectionWidget {} enum AccountSelectAction { RemoveAccount { _index: usize }, @@ -28,36 +25,24 @@ struct AccountSelectResponse { action: Option<AccountSelectAction>, } -impl<'a> AccountSelectionWidget<'a> { - pub fn new( - is_mobile: bool, - account_manager: &'a AccountManager, - simple_preview_controller: SimpleProfilePreviewController<'a>, - ) -> Self { - AccountSelectionWidget { - is_mobile, - account_manager, - simple_preview_controller, - } - } - - pub fn ui(&'a mut self, ui: &mut egui::Ui) { - if self.is_mobile { - self.show_mobile(ui); +impl AccountSelectionWidget { + pub fn ui(app: &mut Damus, ui: &mut egui::Ui) { + if app.is_mobile() { + Self::show_mobile(ui); } else { - self.show(ui); + Self::show(app, ui); } } - fn show(&mut self, ui: &mut egui::Ui) -> AccountSelectResponse { + fn show(app: &mut Damus, ui: &mut egui::Ui) -> AccountSelectResponse { let mut res = AccountSelectResponse::default(); - let mut selected_index = self.account_manager.get_selected_account_index(); + let mut selected_index = app.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) { + if let Some(_index) = Self::show_accounts(app, ui) { selected_index = Some(_index); res.action = Some(AccountSelectAction::SelectAccount { _index }); } @@ -66,9 +51,9 @@ impl<'a> AccountSelectionWidget<'a> { ui.add(add_account_button()); if let Some(_index) = selected_index { - if let Some(account) = self.account_manager.get_account(_index) { + if let Some(account) = app.account_manager.get_account(_index) { ui.add_space(8.0); - if self.handle_sign_out(ui, account) { + if Self::handle_sign_out(&app.ndb, ui, account) { res.action = Some(AccountSelectAction::RemoveAccount { _index }) } } @@ -80,29 +65,30 @@ impl<'a> AccountSelectionWidget<'a> { res } - fn handle_sign_out(&mut self, ui: &mut egui::Ui, account: &UserAccount) -> bool { - if let Ok(response) = self.sign_out_button(ui, account) { + fn handle_sign_out(ndb: &Ndb, ui: &mut egui::Ui, account: &UserAccount) -> bool { + if let Ok(response) = Self::sign_out_button(ndb, ui, account) { response.clicked() } else { false } } - fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response { + fn show_mobile(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 show_accounts(app: &mut Damus, ui: &mut egui::Ui) -> Option<usize> { + profile_preview_controller::view_profile_previews(app, 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( + fn sign_out_button( + ndb: &Ndb, + ui: &mut egui::Ui, + account: &UserAccount, + ) -> Result<egui::Response> { + profile_preview_controller::show_with_nickname( + ndb, ui, account.pubkey.bytes(), |ui: &mut egui::Ui, username: &DisplayName| { @@ -122,42 +108,40 @@ impl<'a> AccountSelectionWidget<'a> { } } -fn account_switcher_card_ui() -> fn( +fn account_switcher_card_ui( 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()); - }); + 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 - }); + }); + } else { + ui.add_space(8.0); + ui.add(preview); + } + }); + }) + .response + }); - ui.interact(resp.rect, Id::new(index), Sense::click()) - .clicked() - } + ui.interact(resp.rect, Id::new(index), Sense::click()) + .clicked() } fn selection_widget() -> impl egui::Widget { @@ -215,48 +199,33 @@ fn add_account_button() -> egui::Button<'static> { } mod previews { - use nostrdb::Ndb; use crate::{ - account_manager::AccountManager, - imgcache::ImageCache, test_data, - ui::{profile::SimpleProfilePreviewController, Preview, PreviewConfig, View}, + ui::{Preview, PreviewConfig, View}, + Damus, }; use super::AccountSelectionWidget; pub struct AccountSelectionPreview { - is_mobile: bool, - account_manager: AccountManager, - ndb: Ndb, - img_cache: ImageCache, + app: Damus, } impl AccountSelectionPreview { fn new(is_mobile: bool) -> Self { - let (account_manager, ndb, img_cache) = test_data::get_accmgr_and_ndb_and_imgcache(); - AccountSelectionPreview { - is_mobile, - account_manager, - ndb, - img_cache, - } + let app = test_data::get_account_manager_test_app(is_mobile); + AccountSelectionPreview { app } } } impl View for AccountSelectionPreview { fn ui(&mut self, ui: &mut egui::Ui) { - AccountSelectionWidget::new( - self.is_mobile, - &self.account_manager, - SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), - ) - .ui(ui); + AccountSelectionWidget::show(&mut self.app, ui); } } - impl<'a> Preview for AccountSelectionWidget<'a> { + impl Preview for AccountSelectionWidget { type Prev = AccountSelectionPreview; fn preview(cfg: PreviewConfig) -> Self::Prev { diff --git a/src/ui/mod.rs b/src/ui/mod.rs @@ -13,11 +13,11 @@ pub mod username; pub use account_management::AccountManagementView; pub use account_switcher::AccountSelectionWidget; pub use mention::Mention; -pub use note::Note; +pub use note::{BarAction, Note, NoteResponse}; pub use preview::{Preview, PreviewApp, PreviewConfig}; -pub use profile::{ProfilePic, ProfilePreview}; +pub use profile::{profile_preview_controller, ProfilePic, ProfilePreview}; pub use relay::RelayView; -pub use side_panel::DesktopSidePanel; +pub use side_panel::{DesktopSidePanel, SidePanelAction}; pub use username::Username; use egui::Margin; diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs @@ -15,12 +15,17 @@ pub struct Note<'a> { flags: NoteOptions, } +pub struct NoteResponse { + pub response: egui::Response, + pub action: Option<BarAction>, +} + impl<'a> egui::Widget for Note<'a> { fn ui(self, ui: &mut egui::Ui) -> egui::Response { if self.app.textmode { self.textmode_ui(ui) } else { - self.standard_ui(ui) + self.show(ui).response } } } @@ -186,103 +191,115 @@ impl<'a> Note<'a> { .response } - pub fn standard_ui(self, ui: &mut egui::Ui) -> egui::Response { + pub fn show(self, ui: &mut egui::Ui) -> NoteResponse { #[cfg(feature = "profiling")] puffin::profile_function!(); let note_key = self.note.key().expect("todo: support non-db notes"); let txn = self.note.txn().expect("todo: support non-db notes"); + let mut note_action: Option<BarAction> = None; + + let response = ui + .with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { + ui.spacing_mut().item_spacing.x = 16.0; + + let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey()); + + match profile + .as_ref() + .ok() + .and_then(|p| p.record().profile()?.picture()) + { + // these have different lifetimes and types, + // so the calls must be separate + Some(pic) => { + let expand_size = 5.0; + let anim_speed = 0.05; + let profile_key = profile.as_ref().unwrap().record().note_key(); + let note_key = note_key.as_u64(); + + if self.app.is_mobile() { + ui.add(ui::ProfilePic::new(&mut self.app.img_cache, pic)); + } else { + let (rect, size) = ui::anim::hover_expand( + ui, + egui::Id::new(ProfileAnimId { + profile_key, + note_key, + }), + ui::ProfilePic::default_size(), + expand_size, + anim_speed, + ); + + ui.put( + rect, + ui::ProfilePic::new(&mut self.app.img_cache, pic).size(size), + ) + .on_hover_ui_at_pointer(|ui| { + ui.set_max_width(300.0); + ui.add(ui::ProfilePreview::new( + profile.as_ref().unwrap(), + &mut self.app.img_cache, + )); + }); + } + } + None => { + ui.add(ui::ProfilePic::new( + &mut self.app.img_cache, + ui::ProfilePic::no_pfp_url(), + )); + } + } - ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { - ui.spacing_mut().item_spacing.x = 16.0; - - let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey()); - - match profile - .as_ref() - .ok() - .and_then(|p| p.record().profile()?.picture()) - { - // these have different lifetimes and types, - // so the calls must be separate - Some(pic) => { - let expand_size = 5.0; - let anim_speed = 0.05; - let profile_key = profile.as_ref().unwrap().record().note_key(); - let note_key = note_key.as_u64(); - - if self.app.is_mobile() { - ui.add(ui::ProfilePic::new(&mut self.app.img_cache, pic)); - } else { - let (rect, size) = ui::anim::hover_expand( - ui, - egui::Id::new(ProfileAnimId { - profile_key, - note_key, - }), - ui::ProfilePic::default_size(), - expand_size, - anim_speed, + ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 2.0; + ui.add( + ui::Username::new(profile.as_ref().ok(), self.note.pubkey()) + .abbreviated(20), ); - ui.put( - rect, - ui::ProfilePic::new(&mut self.app.img_cache, pic).size(size), - ) - .on_hover_ui_at_pointer(|ui| { - ui.set_max_width(300.0); - ui.add(ui::ProfilePreview::new( - profile.as_ref().unwrap(), - &mut self.app.img_cache, - )); - }); - } - } - None => { - ui.add(ui::ProfilePic::new( - &mut self.app.img_cache, - ui::ProfilePic::no_pfp_url(), + let cached_note = self + .app + .note_cache_mut() + .cached_note_or_insert_mut(note_key, self.note); + render_reltime(ui, cached_note, true); + }); + + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 2.0; + reply_desc(ui, txn, self.app, note_key, self.note); + }); + + ui.add(NoteContents::new( + self.app, + txn, + self.note, + note_key, + self.options(), )); - } - } - - ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 2.0; - ui.add( - ui::Username::new(profile.as_ref().ok(), self.note.pubkey()) - .abbreviated(20), - ); - - let cached_note = self - .app - .note_cache_mut() - .cached_note_or_insert_mut(note_key, self.note); - render_reltime(ui, cached_note, true); - }); - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 2.0; - reply_desc(ui, txn, self.app, note_key, self.note); + if self.options().has_actionbar() { + note_action = render_note_actionbar(ui).inner; + } }); + }) + .response; - ui.add(NoteContents::new( - self.app, - txn, - self.note, - note_key, - self.options(), - )); - - if self.options().has_actionbar() { - render_note_actionbar(ui); - } - }); - }) - .response + NoteResponse { + response, + action: note_action, + } } } -fn render_note_actionbar(ui: &mut egui::Ui) -> egui::InnerResponse<()> { +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum BarAction { + Reply, +} + +fn render_note_actionbar(ui: &mut egui::Ui) -> egui::InnerResponse<Option<BarAction>> { ui.horizontal(|ui| { let img_data = if ui.style().visuals.dark_mode { egui::include_image!("../../../assets/icons/reply.png") @@ -299,9 +316,11 @@ fn render_note_actionbar(ui: &mut egui::Ui) -> egui::InnerResponse<()> { .fill(ui.style().visuals.panel_fill), ) .clicked() - {} - - //if ui.add(egui::Button::new("like")).clicked() {} + { + Some(BarAction::Reply) + } else { + None + } }) } diff --git a/src/ui/profile/mod.rs b/src/ui/profile/mod.rs @@ -1,7 +1,7 @@ pub mod picture; pub mod preview; -mod profile_preview_controller; +pub mod profile_preview_controller; pub use picture::ProfilePic; pub use preview::ProfilePreview; -pub use profile_preview_controller::{ProfilePreviewOp, SimpleProfilePreviewController}; +pub use profile_preview_controller::ProfilePreviewOp; diff --git a/src/ui/profile/profile_preview_controller.rs b/src/ui/profile/profile_preview_controller.rs @@ -1,168 +1,155 @@ use nostrdb::{Ndb, Transaction}; -use crate::{account_manager::AccountManager, imgcache::ImageCache, DisplayName, Result}; +use crate::{Damus, 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, +pub fn set_profile_previews( + app: &mut Damus, + ui: &mut egui::Ui, + add_preview_ui: fn( 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 + 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(&app.ndb) { + txn + } else { + return None; + }; + + for i in 0..app.account_manager.num_accounts() { + let account = if let Some(account) = app.account_manager.get_account(i) { + account } else { - return None; + continue; }; - for i in 0..account_manager.num_accounts() { - let account = if let Some(account) = account_manager.get_account(i) { - account + let profile = + if let Ok(profile) = app.ndb.get_profile_by_pubkey(&txn, account.pubkey.bytes()) { + profile } 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, &mut app.img_cache); - 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 is_selected = if let Some(selected) = app.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; - }; + 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); + match op { + ProfilePreviewOp::RemoveAccount => { + if to_remove.is_none() { + to_remove = Some(Vec::new()); } - ProfilePreviewOp::SwitchTo => account_manager.select_account(i), + to_remove.as_mut().unwrap().push(i); } + ProfilePreviewOp::SwitchTo => app.account_manager.select_account(i), } - - to_remove } - pub fn view_profile_previews( - &mut self, - account_manager: &AccountManager, + to_remove +} + +pub fn view_profile_previews( + app: &mut Damus, + ui: &mut egui::Ui, + add_preview_ui: fn( 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 + 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(&app.ndb) { + txn + } else { + return None; + }; + + for i in 0..app.account_manager.num_accounts() { + let account = if let Some(account) = app.account_manager.get_account(i) { + account } else { - return None; + continue; }; - for i in 0..account_manager.num_accounts() { - let account = if let Some(account) = account_manager.get_account(i) { - account + let profile = + if let Ok(profile) = app.ndb.get_profile_by_pubkey(&txn, account.pubkey.bytes()) { + profile } 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 preview = SimpleProfilePreview::new(&profile, &mut app.img_cache); - let is_selected = if let Some(selected) = account_manager.get_selected_account_index() { - i == selected - } else { - false - }; + let is_selected = if let Some(selected) = app.account_manager.get_selected_account_index() { + i == selected + } else { + false + }; - if add_preview_ui(ui, preview, width, is_selected, i) { - return Some(i); - } + 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))) - } + None +} - 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)), - )); - } +pub fn show_with_nickname( + ndb: &Ndb, + 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(ndb)?; + let profile = ndb.get_profile_by_pubkey(&txn, key)?; + Ok(ui_element(ui, &get_display_name(&profile))) +} + +pub fn show_with_pfp( + app: &mut Damus, + 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(&app.ndb) { + let profile = app.ndb.get_profile_by_pubkey(&txn, key); + + if let Ok(profile) = profile { + return Some(ui_element( + ui, + ProfilePic::new(&mut app.img_cache, get_profile_url(&profile)), + )); } - None } + None } diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs @@ -1,12 +1,17 @@ use egui::{Button, Layout, SidePanel, Vec2, Widget}; -use crate::account_manager::AccountManager; +use crate::{ui::profile_preview_controller, Damus}; -use super::{profile::SimpleProfilePreviewController, ProfilePic, View}; +use super::{ProfilePic, View}; pub struct DesktopSidePanel<'a> { - selected_account: Option<&'a [u8; 32]>, - simple_preview_controller: SimpleProfilePreviewController<'a>, + app: &'a mut Damus, +} + +impl<'a> View for DesktopSidePanel<'a> { + fn ui(&mut self, ui: &mut egui::Ui) { + self.show(ui); + } } #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -28,21 +33,9 @@ impl SidePanelResponse { } } -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 new(app: &'a mut Damus) -> Self { + DesktopSidePanel { app } } pub fn panel() -> SidePanel { @@ -51,7 +44,7 @@ impl<'a> DesktopSidePanel<'a> { .exact_width(40.0) } - pub fn show(self, ui: &mut egui::Ui) -> SidePanelResponse { + pub fn show(&mut self, ui: &mut egui::Ui) -> SidePanelResponse { let dark_mode = ui.ctx().style().visuals.dark_mode; let spacing_amt = 16.0; @@ -77,12 +70,15 @@ impl<'a> DesktopSidePanel<'a> { 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()) - { + fn pfp_button(&mut self, ui: &mut egui::Ui) -> egui::Response { + let selected_account = self.app.account_manager.get_selected_account(); + if let Some(selected_account) = selected_account { + if let Some(response) = profile_preview_controller::show_with_pfp( + self.app, + ui, + &selected_account.pubkey.bytes().clone(), + show_pfp(), + ) { return response; } } @@ -123,10 +119,9 @@ fn add_column_button(dark_mode: bool) -> egui::Button<'static> { } mod preview { - use nostrdb::Ndb; + use crate::{ - imgcache::ImageCache, test_data, ui::{Preview, PreviewConfig}, }; @@ -134,33 +129,25 @@ mod preview { use super::*; pub struct DesktopSidePanelPreview { - account_manager: AccountManager, - ndb: Ndb, - img_cache: ImageCache, + app: Damus, } 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, - } + fn new(is_mobile: bool) -> Self { + let app = test_data::get_account_manager_test_app(is_mobile); + DesktopSidePanelPreview { app } } } impl View for DesktopSidePanelPreview { fn ui(&mut self, ui: &mut egui::Ui) { - let selected_account = self + let _selected_account = self + .app .account_manager .get_selected_account() .map(|x| x.pubkey.bytes()); - let panel = DesktopSidePanel::new( - selected_account, - SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), - ); + let mut panel = DesktopSidePanel::new(&mut self.app); DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); } @@ -169,8 +156,8 @@ mod preview { impl<'a> Preview for DesktopSidePanel<'a> { type Prev = DesktopSidePanelPreview; - fn preview(_cfg: PreviewConfig) -> Self::Prev { - DesktopSidePanelPreview::new() + fn preview(cfg: PreviewConfig) -> Self::Prev { + DesktopSidePanelPreview::new(cfg.is_mobile) } } }