diff --git a/src/app.rs b/src/app.rs index 534191ec..3f07c836 100644 --- a/src/app.rs +++ b/src/app.rs @@ -14,6 +14,7 @@ use crate::{ note::NoteRef, notecache::{CachedNote, NoteCache}, notes_holder::NotesHolderStorage, + profile::Profile, subscriptions::{SubKind, Subscriptions}, thread::Thread, timeline::{Timeline, TimelineId, TimelineKind, ViewFilter}, @@ -55,6 +56,7 @@ pub struct Damus { pub unknown_ids: UnknownIds, pub drafts: Drafts, pub threads: NotesHolderStorage, + pub profiles: NotesHolderStorage, pub img_cache: ImageCache, pub accounts: AccountManager, pub subscriptions: Subscriptions, @@ -711,6 +713,7 @@ impl Damus { subscriptions: Subscriptions::default(), since_optimize: parsed_args.since_optimize, threads: NotesHolderStorage::default(), + profiles: NotesHolderStorage::default(), drafts: Drafts::default(), state: DamusState::Initializing, img_cache: ImageCache::new(imgcache_dir.into()), @@ -792,6 +795,7 @@ impl Damus { subscriptions: Subscriptions::default(), since_optimize: true, threads: NotesHolderStorage::default(), + profiles: NotesHolderStorage::default(), drafts: Drafts::default(), state: DamusState::Initializing, pool: RelayPool::new(), diff --git a/src/column.rs b/src/column.rs index 74159202..0b986323 100644 --- a/src/column.rs +++ b/src/column.rs @@ -1,6 +1,5 @@ use crate::route::{Route, Router}; use crate::timeline::{Timeline, TimelineId}; -use enostr::Pubkey; use indexmap::IndexMap; use std::iter::Iterator; use std::sync::atomic::{AtomicU32, Ordering}; @@ -61,14 +60,6 @@ impl Columns { self.timelines.insert(col_id, timeline); } - pub fn route_profile_timeline(&mut self, col: usize, pubkey: Pubkey, timeline: Timeline) { - self.column_mut(col) - .router_mut() - .route_to(Route::Profile(pubkey, timeline.id)); - - self.timelines.insert(Self::get_new_id(), timeline); - } - pub fn new_column_picker(&mut self) { self.add_column(Column::new(vec![Route::AddColumn])); } diff --git a/src/nav.rs b/src/nav.rs index e878d319..d4c1f074 100644 --- a/src/nav.rs +++ b/src/nav.rs @@ -3,12 +3,13 @@ use crate::{ app_style::{get_font_size, NotedeckTextStyle}, fonts::NamedFontFamily, notes_holder::NotesHolder, + profile::Profile, relay_pool_manager::RelayPoolManager, route::Route, thread::Thread, timeline::{ route::{render_profile_route, render_timeline_route, AfterRouteExecution, TimelineRoute}, - PubkeySource, Timeline, TimelineKind, + Timeline, }, ui::{ self, @@ -115,11 +116,11 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) { None } - Route::Profile(pubkey, id) => render_profile_route( - *id, + Route::Profile(pubkey) => render_profile_route( *pubkey, &app.ndb, &mut app.columns, + &mut app.profiles, &mut app.pool, &mut app.img_cache, &mut app.note_cache, @@ -144,18 +145,19 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) { } AfterRouteExecution::OpenProfile(pubkey) => { - let pubkey_source = match app.accounts.get_selected_account() { - Some(account) if account.pubkey == pubkey => PubkeySource::DeckAuthor, - _ => PubkeySource::Explicit(pubkey), - }; - - if let Some(timeline) = - TimelineKind::profile(pubkey_source).into_timeline(&app.ndb, None) - { - let timeline_id = timeline.id; - app.columns_mut() - .route_profile_timeline(col, pubkey, timeline); - app.subscribe_new_timeline(timeline_id); + app.columns + .column_mut(col) + .router_mut() + .route_to(Route::Profile(pubkey)); + let txn = Transaction::new(&app.ndb).expect("txn"); + if let Some(res) = Profile::open( + &app.ndb, + &txn, + &mut app.pool, + &mut app.profiles, + pubkey.bytes(), + ) { + res.process(&app.ndb, &txn, &mut app.profiles); } } } @@ -163,8 +165,8 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) { if let Some(NavAction::Returned) = nav_response.action { let r = app.columns_mut().column_mut(col).router_mut().pop(); + let txn = Transaction::new(&app.ndb).expect("txn"); if let Some(Route::Timeline(TimelineRoute::Thread(id))) = r { - let txn = Transaction::new(&app.ndb).expect("txn"); let root_id = { crate::note::root_note_id_from_selected_id( &app.ndb, @@ -176,10 +178,14 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) { Thread::unsubscribe_locally(&txn, &app.ndb, &mut app.threads, &mut app.pool, root_id); } - if let Some(Route::Profile(_, id)) = r { - if let Some(timeline) = app.columns.find_timeline(id) { - unsubscribe_timeline(&app.ndb, timeline); - } + if let Some(Route::Profile(pubkey)) = r { + Profile::unsubscribe_locally( + &txn, + &app.ndb, + &mut app.profiles, + &mut app.pool, + pubkey.bytes(), + ); } } else if let Some(NavAction::Navigated) = nav_response.action { let cur_router = app.columns_mut().column_mut(col).router_mut(); diff --git a/src/notes_holder.rs b/src/notes_holder.rs index a3df309f..536a28ce 100644 --- a/src/notes_holder.rs +++ b/src/notes_holder.rs @@ -9,11 +9,18 @@ use crate::{ timeline::TimelineTab, Error, Result, }; -#[derive(Default)] pub struct NotesHolderStorage { pub id_to_object: HashMap<[u8; 32], M>, } +impl Default for NotesHolderStorage { + fn default() -> Self { + NotesHolderStorage { + id_to_object: HashMap::new(), + } + } +} + pub enum Vitality<'a, M> { Fresh(&'a mut M), Stale(&'a mut M), @@ -75,8 +82,10 @@ impl NotesHolderStorage { debug!("found thread with {} notes", notes.len()); } - self.id_to_object - .insert(id.to_owned(), M::new_notes_holder(notes)); + self.id_to_object.insert( + id.to_owned(), + M::new_notes_holder(id, M::filters(id), notes), + ); Vitality::Fresh(self.id_to_object.get_mut(id).unwrap()) } } @@ -87,7 +96,7 @@ pub trait NotesHolder { fn get_view(&mut self) -> &mut TimelineTab; fn filters(for_id: &[u8; 32]) -> Vec; fn filters_since(for_id: &[u8; 32], since: u64) -> Vec; - fn new_notes_holder(notes: Vec) -> Self; + fn new_notes_holder(id: &[u8; 32], filters: Vec, notes: Vec) -> Self; #[must_use = "UnknownIds::update_from_note_refs should be used on this result"] fn poll_notes_into_view(&mut self, txn: &Transaction, ndb: &Ndb) -> Result<()> { diff --git a/src/profile.rs b/src/profile.rs index 77ac8c01..28c97751 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -1,12 +1,12 @@ -use enostr::Filter; +use enostr::{Filter, Pubkey}; use nostrdb::{FilterBuilder, ProfileRecord}; use crate::{ - filter, + filter::{self, FilterState}, multi_subscriber::MultiSubscriber, note::NoteRef, notes_holder::NotesHolder, - timeline::{Timeline, TimelineTab, ViewFilter}, + timeline::{PubkeySource, Timeline, TimelineKind}, }; pub enum DisplayName<'a> { @@ -48,21 +48,18 @@ pub fn get_profile_name<'a>(record: &'a ProfileRecord) -> Option } pub struct Profile { - view: TimelineTab, + pub timeline: Timeline, pub multi_subscriber: Option, } impl Profile { - pub fn new(notes: Vec) -> Self { - let mut cap = ((notes.len() as f32) * 1.5) as usize; - if cap == 0 { - cap = 25; - } - let mut view = TimelineTab::new_with_capacity(ViewFilter::NotesAndReplies, cap); - view.notes = notes; + pub fn new(source: PubkeySource, filters: Vec, notes: Vec) -> Self { + let mut timeline = + Timeline::new(TimelineKind::profile(source), FilterState::ready(filters)); + timeline.current_view_mut().notes = notes; Profile { - view, + timeline, multi_subscriber: None, } } @@ -81,7 +78,7 @@ impl NotesHolder for Profile { } fn get_view(&mut self) -> &mut crate::timeline::TimelineTab { - &mut self.view + self.timeline.current_view_mut() } fn filters(for_id: &[u8; 32]) -> Vec { @@ -98,7 +95,11 @@ impl NotesHolder for Profile { .collect() } - fn new_notes_holder(notes: Vec) -> Self { - Profile::new(notes) + fn new_notes_holder(id: &[u8; 32], filters: Vec, notes: Vec) -> Self { + Profile::new(PubkeySource::Explicit(Pubkey::new(*id)), filters, notes) + } + + fn set_multi_subscriber(&mut self, subscriber: MultiSubscriber) { + self.multi_subscriber = Some(subscriber); } } diff --git a/src/route.rs b/src/route.rs index b8339ac5..9fb0f7fb 100644 --- a/src/route.rs +++ b/src/route.rs @@ -6,7 +6,7 @@ use crate::{ account_manager::AccountsRoute, column::Columns, timeline::{TimelineId, TimelineRoute}, - ui::profile::preview::get_note_users_displayname_string, + ui::profile::preview::{get_note_users_displayname_string, get_profile_displayname_string}, }; /// App routing. These describe different places you can go inside Notedeck. @@ -17,7 +17,7 @@ pub enum Route { Relays, ComposeNote, AddColumn, - Profile(Pubkey, TimelineId), + Profile(Pubkey), } #[derive(Clone)] @@ -97,11 +97,8 @@ impl Route { }, Route::ComposeNote => "Compose Note".to_owned(), Route::AddColumn => "Add Column".to_owned(), - Route::Profile(_, id) => { - let timeline = columns - .find_timeline(*id) - .expect("expected to find timeline"); - timeline.kind.to_title(ndb) + Route::Profile(pubkey) => { + format!("{}'s Profile", get_profile_displayname_string(ndb, pubkey)) } }; @@ -210,7 +207,7 @@ impl fmt::Display for Route { Route::ComposeNote => write!(f, "Compose Note"), Route::AddColumn => write!(f, "Add Column"), - Route::Profile(_, _) => write!(f, "Profile"), + Route::Profile(_) => write!(f, "Profile"), } } } diff --git a/src/thread.rs b/src/thread.rs index 11939db6..00159c54 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -66,7 +66,7 @@ impl NotesHolder for Thread { Thread::filters(for_id) } - fn new_notes_holder(notes: Vec) -> Self { + fn new_notes_holder(_: &[u8; 32], _: Vec, notes: Vec) -> Self { Thread::new(notes) } diff --git a/src/timeline/route.rs b/src/timeline/route.rs index cc12a635..3a5597c2 100644 --- a/src/timeline/route.rs +++ b/src/timeline/route.rs @@ -5,6 +5,7 @@ use crate::{ imgcache::ImageCache, notecache::NoteCache, notes_holder::NotesHolderStorage, + profile::Profile, thread::Thread, timeline::TimelineId, ui::{ @@ -156,10 +157,10 @@ pub fn render_timeline_route( #[allow(clippy::too_many_arguments)] pub fn render_profile_route( - id: TimelineId, pubkey: Pubkey, ndb: &Ndb, columns: &mut Columns, + profiles: &mut NotesHolderStorage, pool: &mut RelayPool, img_cache: &mut ImageCache, note_cache: &mut NoteCache, @@ -168,7 +169,7 @@ pub fn render_profile_route( ui: &mut egui::Ui, ) -> Option { let timeline_response = - ProfileView::new(pubkey, id, columns, ndb, note_cache, img_cache).ui(ui); + ProfileView::new(pubkey, col, profiles, ndb, note_cache, img_cache).ui(ui); if let Some(bar_action) = timeline_response.bar_action { let txn = nostrdb::Transaction::new(ndb).expect("txn"); let mut cur_column = columns.columns_mut(); diff --git a/src/ui/profile/mod.rs b/src/ui/profile/mod.rs index fbba30b7..03cbaf25 100644 --- a/src/ui/profile/mod.rs +++ b/src/ui/profile/mod.rs @@ -8,16 +8,16 @@ pub use picture::ProfilePic; pub use preview::ProfilePreview; use crate::{ - actionbar::TimelineResponse, column::Columns, imgcache::ImageCache, notecache::NoteCache, - timeline::TimelineId, + actionbar::TimelineResponse, imgcache::ImageCache, notecache::NoteCache, + notes_holder::NotesHolderStorage, profile::Profile, }; -use super::TimelineView; +use super::timeline::{tabs_ui, TimelineTabView}; pub struct ProfileView<'a> { pubkey: Pubkey, - timeline_id: TimelineId, - columns: &'a mut Columns, + col_id: usize, + profiles: &'a mut NotesHolderStorage, ndb: &'a Ndb, note_cache: &'a mut NoteCache, img_cache: &'a mut ImageCache, @@ -26,16 +26,16 @@ pub struct ProfileView<'a> { impl<'a> ProfileView<'a> { pub fn new( pubkey: Pubkey, - timeline_id: TimelineId, - columns: &'a mut Columns, + col_id: usize, + profiles: &'a mut NotesHolderStorage, ndb: &'a Ndb, note_cache: &'a mut NoteCache, img_cache: &'a mut ImageCache, ) -> Self { ProfileView { pubkey, - timeline_id, - columns, + col_id, + profiles, ndb, note_cache, img_cache, @@ -43,27 +43,32 @@ impl<'a> ProfileView<'a> { } pub fn ui(&mut self, ui: &mut egui::Ui) -> TimelineResponse { - let scroll_id = egui::Id::new(("profile_scroll", self.timeline_id, self.pubkey)); + let scroll_id = egui::Id::new(("profile_scroll", self.col_id, self.pubkey)); ScrollArea::vertical() .id_source(scroll_id) .show(ui, |ui| { - { - let txn = Transaction::new(self.ndb).expect("txn"); - if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, self.pubkey.bytes()) { - ProfilePreview::new(&profile, self.img_cache).ui(ui); - } + let txn = Transaction::new(self.ndb).expect("txn"); + if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, self.pubkey.bytes()) { + ProfilePreview::new(&profile, self.img_cache).ui(ui); } + let profile = self + .profiles + .notes_holder_mutated(self.ndb, &txn, self.pubkey.bytes()) + .get_ptr(); - TimelineView::new( - self.timeline_id, - self.columns, + profile.timeline.selected_view = tabs_ui(ui); + + TimelineTabView::new( + profile.timeline.current_view(), + false, + false, + &txn, self.ndb, self.note_cache, self.img_cache, - false, ) - .ui_no_scroll(ui) + .show(ui) }) .inner } diff --git a/src/ui/timeline.rs b/src/ui/timeline.rs index d8951af8..9de45587 100644 --- a/src/ui/timeline.rs +++ b/src/ui/timeline.rs @@ -167,7 +167,7 @@ fn timeline_ui_no_scroll( .show(ui) } -fn tabs_ui(ui: &mut egui::Ui) -> i32 { +pub fn tabs_ui(ui: &mut egui::Ui) -> i32 { ui.spacing_mut().item_spacing.y = 0.0; let tab_res = egui_tabs::Tabs::new(2)