add profile preview and implement scrolling

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2024-10-11 16:36:34 -04:00
parent 44948fdff0
commit ce3f24abcd
6 changed files with 143 additions and 86 deletions

View File

@@ -1,5 +1,6 @@
use crate::route::{Route, Router}; use crate::route::{Route, Router};
use crate::timeline::{Timeline, TimelineId}; use crate::timeline::{Timeline, TimelineId};
use enostr::Pubkey;
use indexmap::IndexMap; use indexmap::IndexMap;
use std::iter::Iterator; use std::iter::Iterator;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
@@ -60,10 +61,10 @@ impl Columns {
self.timelines.insert(col_id, timeline); self.timelines.insert(col_id, timeline);
} }
pub fn route_profile_timeline(&mut self, col: usize, timeline: Timeline) { pub fn route_profile_timeline(&mut self, col: usize, pubkey: Pubkey, timeline: Timeline) {
self.column_mut(col) self.column_mut(col)
.router_mut() .router_mut()
.route_to(Route::Profile(timeline.id)); .route_to(Route::Profile(pubkey, timeline.id));
self.timelines.insert(Self::get_new_id(), timeline); self.timelines.insert(Self::get_new_id(), timeline);
} }

View File

@@ -113,8 +113,9 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
None None
} }
Route::Profile(id) => render_profile_route( Route::Profile(pubkey, id) => render_profile_route(
*id, *id,
*pubkey,
&app.ndb, &app.ndb,
&mut app.columns, &mut app.columns,
&mut app.pool, &mut app.pool,
@@ -150,7 +151,8 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
TimelineKind::profile(pubkey_source).into_timeline(&app.ndb, None) TimelineKind::profile(pubkey_source).into_timeline(&app.ndb, None)
{ {
let timeline_id = timeline.id; let timeline_id = timeline.id;
app.columns_mut().route_profile_timeline(col, timeline); app.columns_mut()
.route_profile_timeline(col, pubkey, timeline);
app.subscribe_new_timeline(timeline_id); app.subscribe_new_timeline(timeline_id);
} }
} }

View File

@@ -1,4 +1,4 @@
use enostr::NoteId; use enostr::{NoteId, Pubkey};
use nostrdb::Ndb; use nostrdb::Ndb;
use std::fmt::{self}; use std::fmt::{self};
@@ -17,7 +17,7 @@ pub enum Route {
Relays, Relays,
ComposeNote, ComposeNote,
AddColumn, AddColumn,
Profile(TimelineId), Profile(Pubkey, TimelineId),
} }
#[derive(Clone)] #[derive(Clone)]
@@ -97,7 +97,7 @@ impl Route {
}, },
Route::ComposeNote => "Compose Note".to_owned(), Route::ComposeNote => "Compose Note".to_owned(),
Route::AddColumn => "Add Column".to_owned(), Route::AddColumn => "Add Column".to_owned(),
Route::Profile(id) => { Route::Profile(_, id) => {
let timeline = columns let timeline = columns
.find_timeline(*id) .find_timeline(*id)
.expect("expected to find timeline"); .expect("expected to find timeline");
@@ -210,7 +210,7 @@ impl fmt::Display for Route {
Route::ComposeNote => write!(f, "Compose Note"), Route::ComposeNote => write!(f, "Compose Note"),
Route::AddColumn => write!(f, "Add Column"), Route::AddColumn => write!(f, "Add Column"),
Route::Profile(_) => write!(f, "Profile"), Route::Profile(_, _) => write!(f, "Profile"),
} }
} }
} }

View File

@@ -154,6 +154,7 @@ pub fn render_timeline_route(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn render_profile_route( pub fn render_profile_route(
id: TimelineId, id: TimelineId,
pubkey: Pubkey,
ndb: &Ndb, ndb: &Ndb,
columns: &mut Columns, columns: &mut Columns,
pool: &mut RelayPool, pool: &mut RelayPool,
@@ -163,7 +164,7 @@ pub fn render_profile_route(
col: usize, col: usize,
ui: &mut egui::Ui, ui: &mut egui::Ui,
) -> Option<AfterRouteExecution> { ) -> Option<AfterRouteExecution> {
let timeline_response = ProfileView::new(id, columns, ndb, note_cache, img_cache).ui(ui); let timeline_response = ProfileView::new(pubkey, id, columns, ndb, note_cache, img_cache).ui(ui);
if let Some(bar_action) = timeline_response.bar_action { if let Some(bar_action) = timeline_response.bar_action {
let txn = nostrdb::Transaction::new(ndb).expect("txn"); let txn = nostrdb::Transaction::new(ndb).expect("txn");
let mut cur_column = columns.columns_mut(); let mut cur_column = columns.columns_mut();

View File

@@ -1,11 +1,11 @@
pub mod picture; pub mod picture;
pub mod preview; pub mod preview;
use egui::{Label, RichText}; use egui::{ScrollArea, Widget};
use nostrdb::Ndb; use enostr::Pubkey;
use nostrdb::{Ndb, Transaction};
pub use picture::ProfilePic; pub use picture::ProfilePic;
pub use preview::ProfilePreview; pub use preview::ProfilePreview;
use tracing::info;
use crate::{ use crate::{
actionbar::TimelineResponse, column::Columns, imgcache::ImageCache, notecache::NoteCache, actionbar::TimelineResponse, column::Columns, imgcache::ImageCache, notecache::NoteCache,
@@ -15,6 +15,7 @@ use crate::{
use super::TimelineView; use super::TimelineView;
pub struct ProfileView<'a> { pub struct ProfileView<'a> {
pubkey: Pubkey,
timeline_id: TimelineId, timeline_id: TimelineId,
columns: &'a mut Columns, columns: &'a mut Columns,
ndb: &'a Ndb, ndb: &'a Ndb,
@@ -24,6 +25,7 @@ pub struct ProfileView<'a> {
impl<'a> ProfileView<'a> { impl<'a> ProfileView<'a> {
pub fn new( pub fn new(
pubkey: Pubkey,
timeline_id: TimelineId, timeline_id: TimelineId,
columns: &'a mut Columns, columns: &'a mut Columns,
ndb: &'a Ndb, ndb: &'a Ndb,
@@ -31,6 +33,7 @@ impl<'a> ProfileView<'a> {
img_cache: &'a mut ImageCache, img_cache: &'a mut ImageCache,
) -> Self { ) -> Self {
ProfileView { ProfileView {
pubkey,
timeline_id, timeline_id,
columns, columns,
ndb, ndb,
@@ -40,18 +43,28 @@ impl<'a> ProfileView<'a> {
} }
pub fn ui(&mut self, ui: &mut egui::Ui) -> TimelineResponse { pub fn ui(&mut self, ui: &mut egui::Ui) -> TimelineResponse {
ui.add(Label::new( let scroll_id = egui::Id::new(("profile_scroll", self.timeline_id, self.pubkey));
RichText::new("PROFILE VIEW").text_style(egui::TextStyle::Heading),
));
TimelineView::new( ScrollArea::vertical()
self.timeline_id, .id_source(scroll_id)
self.columns, .show(ui, |ui| {
self.ndb, {
self.note_cache, let txn = Transaction::new(self.ndb).expect("txn");
self.img_cache, if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, self.pubkey.bytes()) {
false, ProfilePreview::new(&profile, self.img_cache).ui(ui);
) }
.ui(ui) }
TimelineView::new(
self.timeline_id,
self.columns,
self.ndb,
self.note_cache,
self.img_cache,
false,
)
.ui_no_scroll(ui)
})
.inner
} }
} }

View File

@@ -54,6 +54,23 @@ impl<'a> TimelineView<'a> {
) )
} }
pub fn ui_no_scroll(&mut self, ui: &mut egui::Ui) -> TimelineResponse {
if let Some(timeline) = self.columns.find_timeline_mut(self.timeline_id) {
timeline.selected_view = tabs_ui(ui);
};
timeline_ui_no_scroll(
ui,
self.ndb,
self.timeline_id,
self.columns,
self.note_cache,
self.img_cache,
self.reverse,
self.textmode,
)
}
pub fn reversed(mut self) -> Self { pub fn reversed(mut self) -> Self {
self.reverse = true; self.reverse = true;
self self
@@ -96,82 +113,105 @@ fn timeline_ui(
egui::Id::new(("tlscroll", timeline.view_id())) egui::Id::new(("tlscroll", timeline.view_id()))
}; };
let mut open_profile: Option<Pubkey> = None;
let mut bar_action: Option<BarAction> = None;
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
.id_source(scroll_id) .id_source(scroll_id)
.animated(false) .animated(false)
.auto_shrink([false, false]) .auto_shrink([false, false])
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible) .scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
.show(ui, |ui| { .show(ui, |ui| {
let timeline = if let Some(timeline) = columns.find_timeline_mut(timeline_id) { timeline_ui_no_scroll(
timeline ui,
ndb,
timeline_id,
columns,
note_cache,
img_cache,
reversed,
textmode,
)
})
.inner
}
#[allow(clippy::too_many_arguments)]
fn timeline_ui_no_scroll(
ui: &mut egui::Ui,
ndb: &Ndb,
timeline_id: TimelineId,
columns: &mut Columns,
note_cache: &mut NoteCache,
img_cache: &mut ImageCache,
reversed: bool,
textmode: bool,
) -> TimelineResponse {
let mut open_profile: Option<Pubkey> = None;
let mut bar_action: Option<BarAction> = None;
let timeline = if let Some(timeline) = columns.find_timeline_mut(timeline_id) {
timeline
} else {
error!("tried to render timeline in column, but timeline was missing");
// TODO (jb55): render error when timeline is missing?
// this shouldn't happen...
return TimelineResponse::default();
};
let view = timeline.current_view();
let len = view.notes.len();
let txn = if let Ok(txn) = Transaction::new(ndb) {
txn
} else {
warn!("failed to create transaction");
return TimelineResponse::default();
};
view.list
.clone()
.borrow_mut()
.ui_custom_layout(ui, len, |ui, start_index| {
ui.spacing_mut().item_spacing.y = 0.0;
ui.spacing_mut().item_spacing.x = 4.0;
let ind = if reversed {
len - start_index - 1
} else { } else {
error!("tried to render timeline in column, but timeline was missing"); start_index
// TODO (jb55): render error when timeline is missing? };
// this shouldn't happen...
let note_key = timeline.current_view().notes[ind].key;
let note = if let Ok(note) = ndb.get_note_by_key(&txn, note_key) {
note
} else {
warn!("failed to query note {:?}", note_key);
return 0; return 0;
}; };
let view = timeline.current_view(); ui::padding(8.0, ui, |ui| {
let len = view.notes.len(); let resp = ui::NoteView::new(ndb, note_cache, img_cache, &note)
let txn = if let Ok(txn) = Transaction::new(ndb) { .note_previews(!textmode)
txn .selectable_text(false)
} else { .options_button(true)
warn!("failed to create transaction"); .show(ui);
return 0;
};
view.list if let Some(ba) = resp.action {
.clone() bar_action = Some(ba);
.borrow_mut() } else if resp.response.clicked() {
.ui_custom_layout(ui, len, |ui, start_index| { debug!("clicked note");
ui.spacing_mut().item_spacing.y = 0.0; }
ui.spacing_mut().item_spacing.x = 4.0;
let ind = if reversed { if let Some(context) = resp.context_selection {
len - start_index - 1 context.process(ui, &note);
} else { }
start_index
};
let note_key = timeline.current_view().notes[ind].key; if resp.clicked_profile {
info!("clicked profile");
open_profile = Some(Pubkey::new(*note.pubkey()))
}
});
let note = if let Ok(note) = ndb.get_note_by_key(&txn, note_key) { ui::hline(ui);
note //ui.add(egui::Separator::default().spacing(0.0));
} else {
warn!("failed to query note {:?}", note_key);
return 0;
};
ui::padding(8.0, ui, |ui| {
let resp = ui::NoteView::new(ndb, note_cache, img_cache, &note)
.note_previews(!textmode)
.selectable_text(false)
.options_button(true)
.show(ui);
if let Some(ba) = resp.action {
bar_action = Some(ba);
} else if resp.response.clicked() {
debug!("clicked note");
}
if let Some(context) = resp.context_selection {
context.process(ui, &note);
}
if resp.clicked_profile {
info!("clicked profile");
open_profile = Some(Pubkey::new(*note.pubkey()))
}
});
ui::hline(ui);
//ui.add(egui::Separator::default().spacing(0.0));
1
});
1 1
}); });