Skip to content

Commit

Permalink
nip10: show initial reply information on notes
Browse files Browse the repository at this point in the history
Using the newly merged nip10 code, we can show replies on notes!

This is not final, and it's actually different than how we do it in
Damus iOS. Not sure if I like it yet. We will likely have to put pubkey
information back in somewhere soon.

Signed-off-by: William Casarin <[email protected]>
  • Loading branch information
jb55 committed Apr 27, 2024
1 parent afb0e5f commit ae732f3
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 71 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ serde_json = "1.0.89"
env_logger = "0.10.0"
puffin_egui = { version = "0.27.0", optional = true }
puffin = { version = "0.19.0", optional = true }
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f" }
#nostrdb = "0.3.2"
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "99d8296fcba5957245ed883e2f3b1c0d1cb16397" }
#nostrdb = "0.3.3"
hex = "0.4.3"
base32 = "0.4.0"
nostr-sdk = "0.29.0"
Expand Down
4 changes: 2 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,10 +499,10 @@ impl Damus {
}
}

pub fn get_note_cache_mut(&mut self, note_key: NoteKey, created_at: u64) -> &mut NoteCache {
pub fn get_note_cache_mut(&mut self, note_key: NoteKey, note: &Note<'_>) -> &mut NoteCache {
self.note_cache
.entry(note_key)
.or_insert_with(|| NoteCache::new(created_at))
.or_insert_with(|| NoteCache::new(note))
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/notecache.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
use crate::time::time_ago_since;
use crate::timecache::TimeCached;
use nostrdb::{Note, NoteReply, NoteReplyBuf};
use std::time::Duration;

pub struct NoteCache {
reltime: TimeCached<String>,
pub reply: NoteReplyBuf,
pub bar_open: bool,
}

impl NoteCache {
pub fn new(created_at: u64) -> Self {
pub fn new(note: &Note<'_>) -> Self {
let created_at = note.created_at();
let reltime = TimeCached::new(
Duration::from_secs(1),
Box::new(move || time_ago_since(created_at)),
);
let reply = NoteReply::new(note.tags()).to_owned();
let bar_open = false;
NoteCache { reltime, bar_open }
NoteCache {
reltime,
reply,
bar_open,
}
}

pub fn reltime_str(&mut self) -> &str {
Expand Down
61 changes: 61 additions & 0 deletions src/ui/mention.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::{colors, ui, Damus};
use nostrdb::Transaction;

pub struct Mention<'a> {
app: &'a mut Damus,
txn: &'a Transaction,
pk: &'a [u8; 32],
size: f32,
}

impl<'a> Mention<'a> {
pub fn new(app: &'a mut Damus, txn: &'a Transaction, pk: &'a [u8; 32]) -> Self {
let size = 16.0;
Mention { app, txn, pk, size }
}

pub fn size(mut self, size: f32) -> Self {
self.size = size;
self
}
}

impl<'a> egui::Widget for Mention<'a> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
mention_ui(self.app, self.txn, self.pk, ui, self.size)
}
}

fn mention_ui(
app: &mut Damus,
txn: &Transaction,
pk: &[u8; 32],
ui: &mut egui::Ui,
size: f32,
) -> egui::Response {
#[cfg(feature = "profiling")]
puffin::profile_function!();

ui.horizontal(|ui| {
let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok();

let name: String =
if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) {
format!("@{}", name.username())
} else {
"??".to_string()
};

let resp = ui.add(egui::Label::new(
egui::RichText::new(name).color(colors::PURPLE).size(size),
));

if let Some(rec) = profile.as_ref() {
resp.on_hover_ui_at_pointer(|ui| {
ui.set_max_width(300.0);
ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache));
});
}
})
.response
}
2 changes: 2 additions & 0 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod anim;
pub mod mention;
pub mod note;
pub mod preview;
pub mod profile;
pub mod relay;
pub mod username;

pub use mention::Mention;
pub use note::Note;
pub use preview::{Preview, PreviewApp};
pub use profile::{ProfilePic, ProfilePreview};
Expand Down
27 changes: 2 additions & 25 deletions src/ui/note/contents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,29 +97,6 @@ fn render_note_preview(
.response
}

fn mention_ui(app: &mut Damus, txn: &Transaction, pk: &[u8; 32], ui: &mut egui::Ui) {
#[cfg(feature = "profiling")]
puffin::profile_function!();

let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok();

let name: String =
if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) {
format!("@{}", name.username())
} else {
"??".to_string()
};

let resp = ui.colored_label(colors::PURPLE, &name);

if let Some(rec) = profile.as_ref() {
resp.on_hover_ui_at_pointer(|ui| {
ui.set_max_width(300.0);
ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache));
});
}
}

fn render_note_contents(
ui: &mut egui::Ui,
damus: &mut Damus,
Expand Down Expand Up @@ -149,11 +126,11 @@ fn render_note_contents(
match block.blocktype() {
BlockType::MentionBech32 => match block.as_mention().unwrap() {
Mention::Profile(profile) => {
mention_ui(damus, txn, profile.pubkey(), ui);
ui.add(ui::Mention::new(damus, txn, profile.pubkey()));
}

Mention::Pubkey(npub) => {
mention_ui(damus, txn, npub.pubkey(), ui);
ui.add(ui::Mention::new(damus, txn, npub.pubkey()));
}

Mention::Note(note) if options.has_note_previews() => {
Expand Down
162 changes: 125 additions & 37 deletions src/ui/note/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ pub use options::NoteOptions;

use crate::{colors, ui, Damus};
use egui::{Label, RichText, Sense};
use nostrdb::{NoteKey, Transaction};
use std::hash::{Hash, Hasher};

use log::info;

Check failure on line 12 in src/ui/note/mod.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `log::info`

Check failure on line 12 in src/ui/note/mod.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `log::info`

Check warning on line 12 in src/ui/note/mod.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `log::info`

Check warning on line 12 in src/ui/note/mod.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `log::info`

Check warning on line 12 in src/ui/note/mod.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `log::info`

Check warning on line 12 in src/ui/note/mod.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `log::info`

pub struct Note<'a> {
app: &'a mut Damus,
note: &'a nostrdb::Note<'a>,
Expand Down Expand Up @@ -38,6 +41,90 @@ impl Hash for ProfileAnimId {
}
}

fn reply_desc(
ui: &mut egui::Ui,
txn: &Transaction,
app: &mut Damus,
note_key: NoteKey,
note: &nostrdb::Note<'_>,
) {
#[cfg(feature = "profiling")]
puffin::profile_function!();

let note_reply = app
.get_note_cache_mut(note_key, note)
.reply
.borrow(note.tags());

let reply = if let Some(reply) = note_reply.reply() {
reply
} else {
// not a reply, nothing to do here
return;
};

ui.add(Label::new(
RichText::new("replying to")
.size(10.0)
.color(colors::GRAY_SECONDARY),
));

let reply_note = if let Ok(reply_note) = app.ndb.get_note_by_id(txn, reply.id) {
reply_note
} else {
ui.add(Label::new(
RichText::new("a note")
.size(10.0)
.color(colors::GRAY_SECONDARY),
));
return;
};

if note_reply.is_reply_to_root() {
// We're replying to the root, let's show this
ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
ui.add(Label::new(
RichText::new("'s note")
.size(10.0)
.color(colors::GRAY_SECONDARY),
));
} else if let Some(root) = note_reply.root() {
// replying to another post in a thread, not the root

if let Ok(root_note) = app.ndb.get_note_by_id(txn, root.id) {
if root_note.pubkey() == reply_note.pubkey() {
// simply "replying to bob's note" when replying to bob in his thread
ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
ui.add(Label::new(
RichText::new("'s note")
.size(10.0)
.color(colors::GRAY_SECONDARY),
));
} else {
// replying to bob in alice's thread

ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
ui.add(Label::new(
RichText::new("in").size(10.0).color(colors::GRAY_SECONDARY),
));
ui.add(ui::Mention::new(app, txn, root_note.pubkey()).size(10.0));
ui.add(Label::new(
RichText::new("'s thread")
.size(10.0)
.color(colors::GRAY_SECONDARY),
));
}
} else {
ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
ui.add(Label::new(
RichText::new("in someone's thread")
.size(10.0)
.color(colors::GRAY_SECONDARY),
));
}
}
}

impl<'a> Note<'a> {
pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self {
let flags = NoteOptions::actionbar | NoteOptions::note_previews;
Expand Down Expand Up @@ -69,32 +156,30 @@ impl<'a> Note<'a> {
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey());

ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 2.0;

let note_cache = self
.app
.get_note_cache_mut(note_key, self.note.created_at());
//ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 2.0;

let (_id, rect) = ui.allocate_space(egui::vec2(50.0, 20.0));
ui.allocate_rect(rect, Sense::hover());
ui.put(rect, |ui: &mut egui::Ui| {
render_reltime(ui, note_cache, false).response
});
let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0));
ui.allocate_rect(rect, Sense::hover());
ui.put(rect, |ui: &mut egui::Ui| {
ui.add(
ui::Username::new(profile.as_ref().ok(), self.note.pubkey())
.abbreviated(8)
.pk_colored(true),
)
});
let note_cache = self.app.get_note_cache_mut(note_key, self.note);

ui.add(NoteContents::new(
self.app, txn, self.note, note_key, self.flags,
));
let (_id, rect) = ui.allocate_space(egui::vec2(50.0, 20.0));
ui.allocate_rect(rect, Sense::hover());
ui.put(rect, |ui: &mut egui::Ui| {
render_reltime(ui, note_cache, false).response
});
let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0));
ui.allocate_rect(rect, Sense::hover());
ui.put(rect, |ui: &mut egui::Ui| {
ui.add(
ui::Username::new(profile.as_ref().ok(), self.note.pubkey())
.abbreviated(8)
.pk_colored(true),
)
});

ui.add(NoteContents::new(
self.app, txn, self.note, note_key, self.flags,
));
//});
})
.response
}
Expand Down Expand Up @@ -163,12 +248,15 @@ impl<'a> Note<'a> {
.abbreviated(20),
);

let note_cache = self
.app
.get_note_cache_mut(note_key, self.note.created_at());
let note_cache = self.app.get_note_cache_mut(note_key, self.note);
render_reltime(ui, note_cache, 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,
Expand Down Expand Up @@ -210,6 +298,12 @@ fn render_note_actionbar(ui: &mut egui::Ui) -> egui::InnerResponse<()> {
})
}

fn secondary_label(ui: &mut egui::Ui, s: impl Into<String>) {
ui.add(Label::new(
RichText::new(s).size(10.0).color(colors::GRAY_SECONDARY),
));
}

fn render_reltime(
ui: &mut egui::Ui,
note_cache: &mut crate::notecache::NoteCache,
Expand All @@ -220,19 +314,13 @@ fn render_reltime(

ui.horizontal(|ui| {
if before {
ui.add(Label::new(
RichText::new("⋅").size(10.0).color(colors::GRAY_SECONDARY),
));
secondary_label(ui, "⋅");
}
ui.add(Label::new(
RichText::new(note_cache.reltime_str())
.size(10.0)
.color(colors::GRAY_SECONDARY),
));

secondary_label(ui, note_cache.reltime_str());

if !before {
ui.add(Label::new(
RichText::new("⋅").size(10.0).color(colors::GRAY_SECONDARY),
));
secondary_label(ui, "⋅");
}
})
}
Loading

0 comments on commit ae732f3

Please sign in to comment.