diff --git a/src/actionbar.rs b/src/actionbar.rs index e0a4353..a0c91e3 100644 --- a/src/actionbar.rs +++ b/src/actionbar.rs @@ -5,7 +5,7 @@ use crate::{ route::{Route, Router}, thread::{Thread, ThreadResult, Threads}, }; -use enostr::{NoteId, RelayPool}; +use enostr::{NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; #[derive(Debug, Eq, PartialEq, Copy, Clone)] @@ -15,6 +15,12 @@ pub enum BarAction { OpenThread(NoteId), } +#[derive(Default)] +pub struct TimelineResponse { + pub bar_action: Option, + pub open_profile: Option, +} + pub struct NewThreadNotes { pub root_id: NoteId, pub notes: Vec, diff --git a/src/app.rs b/src/app.rs index ce3c234..233d990 100644 --- a/src/app.rs +++ b/src/app.rs @@ -693,7 +693,7 @@ impl Damus { let mut columns: Columns = Columns::new(); for col in parsed_args.columns { if let Some(timeline) = col.into_timeline(&ndb, account) { - columns.add_timeline(timeline); + columns.add_new_timeline_column(timeline); } } @@ -777,7 +777,7 @@ impl Damus { let timeline = Timeline::new(TimelineKind::Universe, FilterState::ready(vec![filter])); - columns.add_timeline(timeline); + columns.add_new_timeline_column(timeline); let imgcache_dir = data_path.as_ref().join(ImageCache::rel_datadir()); let _ = std::fs::create_dir_all(imgcache_dir.clone()); diff --git a/src/column.rs b/src/column.rs index 62d45cd..3a7fc99 100644 --- a/src/column.rs +++ b/src/column.rs @@ -45,7 +45,7 @@ impl Columns { Columns::default() } - pub fn add_timeline(&mut self, timeline: Timeline) { + pub fn add_new_timeline_column(&mut self, timeline: Timeline) { let id = Self::get_new_id(); let routes = vec![Route::timeline(timeline.id)]; self.timelines.insert(id, timeline); @@ -60,6 +60,14 @@ impl Columns { self.timelines.insert(col_id, timeline); } + pub fn route_profile_timeline(&mut self, col: usize, timeline: Timeline) { + self.column_mut(col) + .router_mut() + .route_to(Route::Profile(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 bca1d03..f8c0fa4 100644 --- a/src/nav.rs +++ b/src/nav.rs @@ -5,7 +5,10 @@ use crate::{ relay_pool_manager::RelayPoolManager, route::Route, thread::thread_unsubscribe, - timeline::route::{render_timeline_route, AfterRouteExecution, TimelineRoute}, + timeline::{ + route::{render_profile_route, render_timeline_route, AfterRouteExecution, TimelineRoute}, + PubkeySource, TimelineKind, + }, ui::{ self, add_column::{AddColumnResponse, AddColumnView}, @@ -109,6 +112,18 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) { } None } + + Route::Profile(id) => render_profile_route( + *id, + &app.ndb, + &mut app.columns, + &mut app.pool, + &mut app.img_cache, + &mut app.note_cache, + &mut app.threads, + col, + ui, + ), } }); @@ -124,6 +139,21 @@ 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, timeline); + app.subscribe_new_timeline(timeline_id); + } + } } } diff --git a/src/route.rs b/src/route.rs index 9b622c9..3ee6be4 100644 --- a/src/route.rs +++ b/src/route.rs @@ -17,6 +17,7 @@ pub enum Route { Relays, ComposeNote, AddColumn, + Profile(TimelineId), } #[derive(Clone)] @@ -96,6 +97,12 @@ 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) + } }; TitledRoute { @@ -203,6 +210,7 @@ impl fmt::Display for Route { Route::ComposeNote => write!(f, "Compose Note"), Route::AddColumn => write!(f, "Add Column"), + Route::Profile(_) => write!(f, "Profile"), } } } diff --git a/src/timeline/route.rs b/src/timeline/route.rs index 7c1367c..fa9b898 100644 --- a/src/timeline/route.rs +++ b/src/timeline/route.rs @@ -12,10 +12,11 @@ use crate::{ post::{PostAction, PostResponse}, QuoteRepostView, }, + profile::ProfileView, }, }; -use enostr::{NoteId, RelayPool}; +use enostr::{NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -28,6 +29,7 @@ pub enum TimelineRoute { pub enum AfterRouteExecution { Post(PostResponse), + OpenProfile(Pubkey), } impl AfterRouteExecution { @@ -53,10 +55,10 @@ pub fn render_timeline_route( ) -> Option { match route { TimelineRoute::Timeline(timeline_id) => { - if let Some(bar_action) = + let timeline_response = ui::TimelineView::new(timeline_id, columns, ndb, note_cache, img_cache, textmode) - .ui(ui) - { + .ui(ui); + if let Some(bar_action) = timeline_response.bar_action { let txn = Transaction::new(ndb).expect("txn"); let mut cur_column = columns.columns_mut(); let router = cur_column[col].router_mut(); @@ -64,7 +66,9 @@ pub fn render_timeline_route( bar_action.execute_and_process_result(ndb, router, threads, note_cache, pool, &txn); } - None + timeline_response + .open_profile + .map(AfterRouteExecution::OpenProfile) } TimelineRoute::Thread(id) => { @@ -146,3 +150,29 @@ pub fn render_timeline_route( } } } + +#[allow(clippy::too_many_arguments)] +pub fn render_profile_route( + id: TimelineId, + ndb: &Ndb, + columns: &mut Columns, + pool: &mut RelayPool, + img_cache: &mut ImageCache, + note_cache: &mut NoteCache, + threads: &mut Threads, + col: usize, + ui: &mut egui::Ui, +) -> Option { + let timeline_response = ProfileView::new(id, columns, 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(); + let router = cur_column[col].router_mut(); + + bar_action.execute_and_process_result(ndb, router, threads, note_cache, pool, &txn); + } + + timeline_response + .open_profile + .map(AfterRouteExecution::OpenProfile) +} diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs index 0c83718..e2e57c5 100644 --- a/src/ui/note/mod.rs +++ b/src/ui/note/mod.rs @@ -39,6 +39,7 @@ pub struct NoteResponse { pub response: egui::Response, pub action: Option, pub context_selection: Option, + pub clicked_profile: bool, } impl NoteResponse { @@ -47,6 +48,7 @@ impl NoteResponse { response, action: None, context_selection: None, + clicked_profile: false, } } @@ -60,6 +62,13 @@ impl NoteResponse { ..self } } + + pub fn click_profile(self, clicked_profile: bool) -> Self { + Self { + clicked_profile, + ..self + } + } } impl<'a> View for NoteView<'a> { @@ -305,7 +314,7 @@ impl<'a> NoteView<'a> { note_key: NoteKey, profile: &Result, nostrdb::Error>, ui: &mut egui::Ui, - ) { + ) -> egui::Response { if !self.options().has_wide() { ui.spacing_mut().item_spacing.x = 16.0; } else { @@ -314,6 +323,7 @@ impl<'a> NoteView<'a> { let pfp_size = self.options().pfp_size(); + let sense = Sense::click(); match profile .as_ref() .ok() @@ -326,7 +336,7 @@ impl<'a> NoteView<'a> { let profile_key = profile.as_ref().unwrap().record().note_key(); let note_key = note_key.as_u64(); - let (rect, size, _resp) = ui::anim::hover_expand( + let (rect, size, resp) = ui::anim::hover_expand( ui, egui::Id::new((profile_key, note_key)), pfp_size, @@ -342,13 +352,14 @@ impl<'a> NoteView<'a> { self.img_cache, )); }); + resp } - None => { - ui.add( + None => ui + .add( ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url()) .size(pfp_size), - ); - } + ) + .interact(sense), } } @@ -441,10 +452,12 @@ impl<'a> NoteView<'a> { Pos2::new(x, y) }; + let mut clicked_profile = false; + // wide design let response = if self.options().has_wide() { ui.horizontal(|ui| { - self.pfp(note_key, &profile, ui); + clicked_profile = self.pfp(note_key, &profile, ui).clicked(); let size = ui.available_size(); ui.vertical(|ui| { @@ -498,7 +511,7 @@ impl<'a> NoteView<'a> { } else { // main design ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { - self.pfp(note_key, &profile, ui); + clicked_profile = self.pfp(note_key, &profile, ui).clicked(); ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { selected_option = NoteView::note_header( @@ -557,6 +570,7 @@ impl<'a> NoteView<'a> { NoteResponse::new(response) .with_action(note_action) .select_option(selected_option) + .click_profile(clicked_profile) } } diff --git a/src/ui/profile/mod.rs b/src/ui/profile/mod.rs index dafd7b3..51f1284 100644 --- a/src/ui/profile/mod.rs +++ b/src/ui/profile/mod.rs @@ -1,5 +1,57 @@ pub mod picture; pub mod preview; +use egui::{Label, RichText}; +use nostrdb::Ndb; pub use picture::ProfilePic; pub use preview::ProfilePreview; +use tracing::info; + +use crate::{ + actionbar::TimelineResponse, column::Columns, imgcache::ImageCache, notecache::NoteCache, + timeline::TimelineId, +}; + +use super::TimelineView; + +pub struct ProfileView<'a> { + timeline_id: TimelineId, + columns: &'a mut Columns, + ndb: &'a Ndb, + note_cache: &'a mut NoteCache, + img_cache: &'a mut ImageCache, +} + +impl<'a> ProfileView<'a> { + pub fn new( + timeline_id: TimelineId, + columns: &'a mut Columns, + ndb: &'a Ndb, + note_cache: &'a mut NoteCache, + img_cache: &'a mut ImageCache, + ) -> Self { + ProfileView { + timeline_id, + columns, + ndb, + note_cache, + img_cache, + } + } + + pub fn ui(&mut self, ui: &mut egui::Ui) -> TimelineResponse { + ui.add(Label::new( + RichText::new("PROFILE VIEW").text_style(egui::TextStyle::Heading), + )); + + TimelineView::new( + self.timeline_id, + self.columns, + self.ndb, + self.note_cache, + self.img_cache, + false, + ) + .ui(ui) + } +} diff --git a/src/ui/timeline.rs b/src/ui/timeline.rs index 187c039..2378ef2 100644 --- a/src/ui/timeline.rs +++ b/src/ui/timeline.rs @@ -1,3 +1,4 @@ +use crate::actionbar::TimelineResponse; use crate::{ actionbar::BarAction, column::Columns, imgcache::ImageCache, notecache::NoteCache, timeline::TimelineId, ui, @@ -5,8 +6,9 @@ use crate::{ use egui::containers::scroll_area::ScrollBarVisibility; use egui::{Direction, Layout}; use egui_tabs::TabColor; +use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use tracing::{debug, error, warn}; +use tracing::{debug, error, info, warn}; pub struct TimelineView<'a> { timeline_id: TimelineId, @@ -39,7 +41,7 @@ impl<'a> TimelineView<'a> { } } - pub fn ui(&mut self, ui: &mut egui::Ui) -> Option { + pub fn ui(&mut self, ui: &mut egui::Ui) -> TimelineResponse { timeline_ui( ui, self.ndb, @@ -68,7 +70,7 @@ fn timeline_ui( img_cache: &mut ImageCache, reversed: bool, textmode: bool, -) -> Option { +) -> TimelineResponse { //padding(4.0, ui, |ui| ui.heading("Notifications")); /* let font_id = egui::TextStyle::Body.resolve(ui.style()); @@ -83,7 +85,7 @@ fn timeline_ui( error!("tried to render timeline in column, but timeline was missing"); // TODO (jb55): render error when timeline is missing? // this shouldn't happen... - return None; + return TimelineResponse::default(); }; timeline.selected_view = tabs_ui(ui); @@ -94,6 +96,7 @@ fn timeline_ui( egui::Id::new(("tlscroll", timeline.view_id())) }; + let mut open_profile: Option = None; let mut bar_action: Option = None; egui::ScrollArea::vertical() .id_source(scroll_id) @@ -157,6 +160,11 @@ fn timeline_ui( if let Some(context) = resp.context_selection { context.process(ui, ¬e); } + + if resp.clicked_profile { + info!("clicked profile"); + open_profile = Some(Pubkey::new(*note.pubkey())) + } }); ui::hline(ui); @@ -168,7 +176,10 @@ fn timeline_ui( 1 }); - bar_action + TimelineResponse { + open_profile, + bar_action, + } } fn tabs_ui(ui: &mut egui::Ui) -> i32 {