notedeck

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

commit 212c296da515918a6491cf415c031615d9396036
parent 16b20568da95ad37847525569a398dbea8201a89
Author: William Casarin <jb55@jb55.com>
Date:   Sat,  4 Jan 2025 14:08:33 -0800

Merge tombstone muted notes #606

Changelog-Changed: Tombstone muted notes

Diffstat:
MCargo.lock | 1+
Mcrates/notedeck/src/accounts.rs | 10++++++----
Mcrates/notedeck/src/muted.rs | 31++++++++++++++++++++++++-------
Mcrates/notedeck_chrome/Cargo.toml | 2++
Mcrates/notedeck_chrome/src/theme.rs | 21+++++++++++++++++++--
Mcrates/notedeck_columns/src/actionbar.rs | 39+++++++++------------------------------
Mcrates/notedeck_columns/src/app.rs | 3---
Mcrates/notedeck_columns/src/multi_subscriber.rs | 13++-----------
Mcrates/notedeck_columns/src/nav.rs | 3---
Mcrates/notedeck_columns/src/notes_holder.rs | 15+++++----------
Mcrates/notedeck_columns/src/profile.rs | 7++-----
Mcrates/notedeck_columns/src/thread.rs | 3+--
Mcrates/notedeck_columns/src/timeline/mod.rs | 29+++++++----------------------
Mcrates/notedeck_columns/src/timeline/route.rs | 7+++++--
Mcrates/notedeck_columns/src/ui/add_column.rs | 1-
Mcrates/notedeck_columns/src/ui/profile/mod.rs | 17++++++++---------
Mcrates/notedeck_columns/src/ui/thread.rs | 10+++++++---
Mcrates/notedeck_columns/src/ui/timeline.rs | 51++++++++++++++++++++++++++++++++++++++-------------
18 files changed, 136 insertions(+), 127 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1032,6 +1032,7 @@ checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" dependencies = [ "accesskit", "ahash", + "backtrace", "emath", "epaint", "log", diff --git a/crates/notedeck/src/accounts.rs b/crates/notedeck/src/accounts.rs @@ -1,7 +1,7 @@ use tracing::{debug, error, info}; use crate::{ - KeyStorageResponse, KeyStorageType, Muted, SingleUnkIdAction, UnknownIds, UserAccount, + KeyStorageResponse, KeyStorageType, MuteFun, Muted, SingleUnkIdAction, UnknownIds, UserAccount, }; use enostr::{ClientMessage, FilledKeypair, Keypair, RelayPool}; use nostrdb::{Filter, Ndb, Note, NoteKey, Subscription, Transaction}; @@ -409,17 +409,19 @@ impl Accounts { self.key_store.select_key(None); } - pub fn mutefun(&self) -> Box<dyn Fn(&Note) -> bool> { + pub fn mutefun(&self) -> Box<MuteFun> { if let Some(index) = self.currently_selected_account { if let Some(account) = self.accounts.get(index) { let pubkey = account.pubkey.bytes(); if let Some(account_data) = self.account_data.get(pubkey) { let muted = Arc::clone(&account_data.muted.muted); - return Box::new(move |note: &Note| muted.is_muted(note)); + return Box::new(move |note: &Note, thread: &[u8; 32]| { + muted.is_muted(note, thread) + }); } } } - Box::new(|_: &Note| false) + Box::new(|_: &Note, _: &[u8; 32]| None) } pub fn send_initial_filters(&mut self, pool: &mut RelayPool, relay_url: &str) { diff --git a/crates/notedeck/src/muted.rs b/crates/notedeck/src/muted.rs @@ -1,9 +1,10 @@ use nostrdb::Note; use std::collections::BTreeSet; -use tracing::debug; +use tracing::{debug, trace}; -pub type MuteFun = dyn Fn(&Note) -> bool; +// If the note is muted return a reason string, otherwise None +pub type MuteFun = dyn Fn(&Note, &[u8; 32]) -> Option<String>; #[derive(Default)] pub struct Muted { @@ -32,14 +33,21 @@ impl std::fmt::Debug for Muted { } impl Muted { - pub fn is_muted(&self, note: &Note) -> bool { + // If the note is muted return a reason string, otherwise None + pub fn is_muted(&self, note: &Note, thread: &[u8; 32]) -> Option<String> { + trace!( + "{}: thread: {}", + hex::encode(note.id()), + hex::encode(thread) + ); + if self.pubkeys.contains(note.pubkey()) { debug!( "{}: MUTED pubkey: {}", hex::encode(note.id()), hex::encode(note.pubkey()) ); - return true; + return Some(format!("pubkey {}", hex::encode(note.pubkey()))); } // FIXME - Implement hashtag muting here @@ -51,11 +59,20 @@ impl Muted { // for word in &self.words { // if content.contains(&word.to_lowercase()) { // debug!("{}: MUTED word: {}", hex::encode(note.id()), word); - // return true; + // return Some(format!("muted word {}", word)); // } // } - // FIXME - Implement thread muting here - false + if self.threads.contains(thread) { + debug!( + "{}: MUTED thread: {}", + hex::encode(note.id()), + hex::encode(thread) + ); + return Some(format!("thread {}", hex::encode(thread))); + } + + // if we get here it's not muted + None } } diff --git a/crates/notedeck_chrome/Cargo.toml b/crates/notedeck_chrome/Cargo.toml @@ -42,6 +42,8 @@ path = "src/preview.rs" [features] default = [] profiling = ["notedeck_columns/puffin", "puffin", "puffin_egui"] +debug-widget-callstack = ["egui/callstack"] +debug-interactive-widgets = [] [target.'cfg(target_os = "android")'.dependencies] tracing-logcat = "0.1.0" diff --git a/crates/notedeck_chrome/src/theme.rs b/crates/notedeck_chrome/src/theme.rs @@ -124,9 +124,26 @@ pub fn add_custom_style(is_mobile: bool, style: &mut Style) { ..Interaction::default() }; - #[cfg(debug_assertions)] + // debug: show callstack for the current widget on hover if all + // modifier keys are pressed down. + #[cfg(feature = "debug-widget-callstack")] { - style.debug.show_interactive_widgets = true; + #[cfg(not(debug_assertions))] + compile_error!( + "The `debug-widget-callstack` feature requires a debug build, \ + release builds are unsupported." + ); style.debug.debug_on_hover_with_all_modifiers = true; } + + // debug: show an overlay on all interactive widgets + #[cfg(feature = "debug-interactive-widgets")] + { + #[cfg(not(debug_assertions))] + compile_error!( + "The `debug-interactive-widgets` feature requires a debug build, \ + release builds are unsupported." + ); + style.debug.show_interactive_widgets = true; + } } diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs @@ -8,7 +8,7 @@ use crate::{ use enostr::{NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::{note::root_note_id_from_selected_id, MuteFun, NoteCache, NoteRef}; +use notedeck::{note::root_note_id_from_selected_id, NoteCache, NoteRef}; #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum NoteAction { @@ -41,12 +41,11 @@ fn open_thread( pool: &mut RelayPool, threads: &mut NotesHolderStorage<Thread>, selected_note: &[u8; 32], - is_muted: &MuteFun, ) -> Option<NotesHolderResult> { router.route_to(Route::thread(NoteId::new(selected_note.to_owned()))); let root_id = root_note_id_from_selected_id(ndb, note_cache, txn, selected_note); - Thread::open(ndb, note_cache, txn, pool, threads, root_id, is_muted) + Thread::open(ndb, note_cache, txn, pool, threads, root_id) } impl NoteAction { @@ -60,7 +59,6 @@ impl NoteAction { note_cache: &mut NoteCache, pool: &mut RelayPool, txn: &Transaction, - is_muted: &MuteFun, ) -> Option<NotesHolderResult> { match self { NoteAction::Reply(note_id) => { @@ -68,28 +66,13 @@ impl NoteAction { None } - NoteAction::OpenThread(note_id) => open_thread( - ndb, - txn, - router, - note_cache, - pool, - threads, - note_id.bytes(), - is_muted, - ), + NoteAction::OpenThread(note_id) => { + open_thread(ndb, txn, router, note_cache, pool, threads, note_id.bytes()) + } NoteAction::OpenProfile(pubkey) => { router.route_to(Route::profile(pubkey)); - Profile::open( - ndb, - note_cache, - txn, - pool, - profiles, - pubkey.bytes(), - is_muted, - ) + Profile::open(ndb, note_cache, txn, pool, profiles, pubkey.bytes()) } NoteAction::Quote(note_id) => { @@ -111,13 +94,10 @@ impl NoteAction { note_cache: &mut NoteCache, pool: &mut RelayPool, txn: &Transaction, - is_muted: &MuteFun, ) { let router = columns.column_mut(col).router_mut(); - if let Some(br) = self.execute( - ndb, router, threads, profiles, note_cache, pool, txn, is_muted, - ) { - br.process(ndb, note_cache, txn, threads, is_muted); + if let Some(br) = self.execute(ndb, router, threads, profiles, note_cache, pool, txn) { + br.process(ndb, note_cache, txn, threads); } } } @@ -133,13 +113,12 @@ impl NotesHolderResult { note_cache: &mut NoteCache, txn: &Transaction, storage: &mut NotesHolderStorage<N>, - is_muted: &MuteFun, ) { match self { // update the thread for next render if we have new notes NotesHolderResult::NewNotes(new_notes) => { let holder = storage - .notes_holder_mutated(ndb, note_cache, txn, &new_notes.id, is_muted) + .notes_holder_mutated(ndb, note_cache, txn, &new_notes.id) .get_ptr(); new_notes.process(holder); } diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs @@ -140,7 +140,6 @@ fn try_process_event( app_ctx.pool, app_ctx.note_cache, timeline, - &app_ctx.accounts.mutefun(), app_ctx .accounts .get_selected_account() @@ -159,7 +158,6 @@ fn try_process_event( &txn, app_ctx.unknown_ids, app_ctx.note_cache, - &app_ctx.accounts.mutefun(), ) { error!("poll_notes_into_view: {err}"); } @@ -200,7 +198,6 @@ fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ctx: &egui::Con app_ctx.ndb, app_ctx.note_cache, &mut damus.decks_cache, - &app_ctx.accounts.mutefun(), ) { warn!("update_damus init: {err}"); } diff --git a/crates/notedeck_columns/src/multi_subscriber.rs b/crates/notedeck_columns/src/multi_subscriber.rs @@ -4,7 +4,7 @@ use tracing::{debug, error, info}; use uuid::Uuid; use crate::Error; -use notedeck::{MuteFun, NoteRef, UnifiedSubscription}; +use notedeck::{NoteRef, UnifiedSubscription}; pub struct MultiSubscriber { filters: Vec<Filter>, @@ -106,12 +106,7 @@ impl MultiSubscriber { } } - pub fn poll_for_notes( - &mut self, - ndb: &Ndb, - txn: &Transaction, - is_muted: &MuteFun, - ) -> Result<Vec<NoteRef>, Error> { + pub fn poll_for_notes(&mut self, ndb: &Ndb, txn: &Transaction) -> Result<Vec<NoteRef>, Error> { let sub = self.sub.as_ref().ok_or(notedeck::Error::no_active_sub())?; let new_note_keys = ndb.poll_for_notes(sub.local, 500); @@ -129,10 +124,6 @@ impl MultiSubscriber { continue; }; - if is_muted(&note) { - continue; - } - notes.push(note); } diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs @@ -164,7 +164,6 @@ impl RenderNavResponse { ctx.note_cache, ctx.pool, &txn, - &ctx.accounts.mutefun(), ); } @@ -209,7 +208,6 @@ impl RenderNavResponse { &mut app.threads, ctx.pool, root_id, - &ctx.accounts.mutefun(), ); } @@ -221,7 +219,6 @@ impl RenderNavResponse { &mut app.profiles, ctx.pool, pubkey.bytes(), - &ctx.accounts.mutefun(), ); } diff --git a/crates/notedeck_columns/src/notes_holder.rs b/crates/notedeck_columns/src/notes_holder.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use enostr::{Filter, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::{MuteFun, NoteCache, NoteRef, NoteRefsUnkIdAction}; +use notedeck::{NoteCache, NoteRef, NoteRefsUnkIdAction}; use tracing::{debug, info, warn}; use crate::{ @@ -56,7 +56,6 @@ impl<M: NotesHolder> NotesHolderStorage<M> { note_cache: &mut NoteCache, txn: &Transaction, id: &[u8; 32], - is_muted: &MuteFun, ) -> Vitality<'a, M> { // we can't use the naive hashmap entry API here because lookups // require a copy, wait until we have a raw entry api. We could @@ -90,7 +89,7 @@ impl<M: NotesHolder> NotesHolderStorage<M> { self.id_to_object.insert( id.to_owned(), - M::new_notes_holder(txn, ndb, note_cache, id, M::filters(id), notes, is_muted), + M::new_notes_holder(txn, ndb, note_cache, id, M::filters(id), notes), ); Vitality::Fresh(self.id_to_object.get_mut(id).unwrap()) } @@ -109,7 +108,6 @@ pub trait NotesHolder { id: &[u8; 32], filters: Vec<Filter>, notes: Vec<NoteRef>, - is_muted: &MuteFun, ) -> Self; #[must_use = "process_action must be handled in the Ok(action) case"] @@ -117,11 +115,10 @@ pub trait NotesHolder { &mut self, txn: &Transaction, ndb: &Ndb, - is_muted: &MuteFun, ) -> Result<NoteRefsUnkIdAction> { if let Some(multi_subscriber) = self.get_multi_subscriber() { let reversed = true; - let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn, is_muted)?; + let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn)?; self.get_view().insert(&note_refs, reversed); Ok(NoteRefsUnkIdAction::new(note_refs)) } else { @@ -160,10 +157,9 @@ pub trait NotesHolder { notes_holder_storage: &mut NotesHolderStorage<M>, pool: &mut RelayPool, id: &[u8; 32], - is_muted: &MuteFun, ) { let notes_holder = notes_holder_storage - .notes_holder_mutated(ndb, note_cache, txn, id, is_muted) + .notes_holder_mutated(ndb, note_cache, txn, id) .get_ptr(); if let Some(multi_subscriber) = notes_holder.get_multi_subscriber() { @@ -178,9 +174,8 @@ pub trait NotesHolder { pool: &mut RelayPool, storage: &mut NotesHolderStorage<M>, id: &[u8; 32], - is_muted: &MuteFun, ) -> Option<NotesHolderResult> { - let vitality = storage.notes_holder_mutated(ndb, note_cache, txn, id, is_muted); + let vitality = storage.notes_holder_mutated(ndb, note_cache, txn, id); let (holder, result) = match vitality { Vitality::Stale(holder) => { diff --git a/crates/notedeck_columns/src/profile.rs b/crates/notedeck_columns/src/profile.rs @@ -5,7 +5,7 @@ use nostrdb::{ FilterBuilder, Ndb, Note, NoteBuildOptions, NoteBuilder, ProfileRecord, Transaction, }; -use notedeck::{filter::default_limit, FilterState, MuteFun, NoteCache, NoteRef}; +use notedeck::{filter::default_limit, FilterState, NoteCache, NoteRef}; use tracing::info; use crate::{ @@ -91,7 +91,6 @@ impl Profile { source: PubkeySource, filters: Vec<Filter>, notes: Vec<NoteRef>, - is_muted: &MuteFun, ) -> Self { let mut timeline = Timeline::new( TimelineKind::profile(source), @@ -99,7 +98,7 @@ impl Profile { TimelineTab::full_tabs(), ); - copy_notes_into_timeline(&mut timeline, txn, ndb, note_cache, notes, is_muted); + copy_notes_into_timeline(&mut timeline, txn, ndb, note_cache, notes); Profile { timeline, @@ -145,7 +144,6 @@ impl NotesHolder for Profile { id: &[u8; 32], filters: Vec<Filter>, notes: Vec<NoteRef>, - is_muted: &MuteFun, ) -> Self { Profile::new( txn, @@ -154,7 +152,6 @@ impl NotesHolder for Profile { PubkeySource::Explicit(Pubkey::new(*id)), filters, notes, - is_muted, ) } diff --git a/crates/notedeck_columns/src/thread.rs b/crates/notedeck_columns/src/thread.rs @@ -5,7 +5,7 @@ use crate::{ }; use nostrdb::{Filter, FilterBuilder, Ndb, Transaction}; -use notedeck::{MuteFun, NoteCache, NoteRef}; +use notedeck::{NoteCache, NoteRef}; #[derive(Default)] pub struct Thread { @@ -74,7 +74,6 @@ impl NotesHolder for Thread { _: &[u8; 32], _: Vec<Filter>, notes: Vec<NoteRef>, - _: &MuteFun, ) -> Self { Thread::new(notes) } diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs @@ -7,8 +7,7 @@ use crate::{ }; use notedeck::{ - filter, CachedNote, FilterError, FilterState, FilterStates, MuteFun, NoteCache, NoteRef, - UnknownIds, + filter, CachedNote, FilterError, FilterState, FilterStates, NoteCache, NoteRef, UnknownIds, }; use std::fmt; @@ -287,7 +286,6 @@ impl Timeline { txn: &Transaction, unknown_ids: &mut UnknownIds, note_cache: &mut NoteCache, - is_muted: &MuteFun, ) -> Result<()> { let timeline = timelines .get_mut(timeline_idx) @@ -312,9 +310,6 @@ impl Timeline { error!("hit race condition in poll_notes_into_view: https://github.com/damus-io/nostrdb/issues/35 note {:?} was not added to timeline", key); continue; }; - if is_muted(&note) { - continue; - } UnknownIds::update_from_note(txn, ndb, unknown_ids, note_cache, &note); @@ -412,12 +407,11 @@ pub fn setup_new_timeline( pool: &mut RelayPool, note_cache: &mut NoteCache, since_optimize: bool, - is_muted: &MuteFun, our_pk: Option<&Pubkey>, ) { // if we're ready, setup local subs - if is_timeline_ready(ndb, pool, note_cache, timeline, is_muted, our_pk) { - if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline, is_muted) { + if is_timeline_ready(ndb, pool, note_cache, timeline, our_pk) { + if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline) { error!("setup_new_timeline: {err}"); } } @@ -547,7 +541,6 @@ fn setup_initial_timeline( timeline: &mut Timeline, note_cache: &mut NoteCache, filters: &[Filter], - is_muted: &MuteFun, ) -> Result<()> { timeline.subscription = Some(ndb.subscribe(filters)?); let txn = Transaction::new(ndb)?; @@ -562,7 +555,7 @@ fn setup_initial_timeline( .map(NoteRef::from_query_result) .collect(); - copy_notes_into_timeline(timeline, &txn, ndb, note_cache, notes, is_muted); + copy_notes_into_timeline(timeline, &txn, ndb, note_cache, notes); Ok(()) } @@ -573,7 +566,6 @@ pub fn copy_notes_into_timeline( ndb: &Ndb, note_cache: &mut NoteCache, notes: Vec<NoteRef>, - is_muted: &MuteFun, ) { let filters = { let views = &timeline.views; @@ -585,9 +577,6 @@ pub fn copy_notes_into_timeline( for note_ref in notes { for (view, filter) in filters.iter().enumerate() { if let Ok(note) = ndb.get_note_by_key(txn, note_ref.key) { - if is_muted(&note) { - continue; - } if filter( note_cache.cached_note_or_insert_mut(note_ref.key, &note), &note, @@ -603,12 +592,11 @@ pub fn setup_initial_nostrdb_subs( ndb: &Ndb, note_cache: &mut NoteCache, decks_cache: &mut DecksCache, - is_muted: &MuteFun, ) -> Result<()> { for decks in decks_cache.get_all_decks_mut() { for deck in decks.decks_mut() { for timeline in deck.columns_mut().timelines_mut() { - if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline, is_muted) { + if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline) { error!("setup_initial_nostrdb_subs: {err}"); } } @@ -622,7 +610,6 @@ fn setup_timeline_nostrdb_sub( ndb: &Ndb, note_cache: &mut NoteCache, timeline: &mut Timeline, - is_muted: &MuteFun, ) -> Result<()> { let filter_state = timeline .filter @@ -630,7 +617,7 @@ fn setup_timeline_nostrdb_sub( .ok_or(Error::App(notedeck::Error::empty_contact_list()))? .to_owned(); - setup_initial_timeline(ndb, timeline, note_cache, &filter_state, is_muted)?; + setup_initial_timeline(ndb, timeline, note_cache, &filter_state)?; Ok(()) } @@ -644,7 +631,6 @@ pub fn is_timeline_ready( pool: &mut RelayPool, note_cache: &mut NoteCache, timeline: &mut Timeline, - is_muted: &MuteFun, our_pk: Option<&Pubkey>, ) -> bool { // TODO: we should debounce the filter states a bit to make sure we have @@ -705,8 +691,7 @@ pub fn is_timeline_ready( // we just switched to the ready state, we should send initial // queries and setup the local subscription info!("Found contact list! Setting up local and remote contact list query"); - setup_initial_timeline(ndb, timeline, note_cache, &filter, is_muted) - .expect("setup init"); + setup_initial_timeline(ndb, timeline, note_cache, &filter).expect("setup init"); timeline .filter .set_relay_state(relay_id, FilterState::ready(filter.clone())); diff --git a/crates/notedeck_columns/src/timeline/route.rs b/crates/notedeck_columns/src/timeline/route.rs @@ -63,6 +63,7 @@ pub fn render_timeline_route( note_cache, img_cache, note_options, + &accounts.mutefun(), ) .ui(ui); @@ -77,9 +78,10 @@ pub fn render_timeline_route( img_cache, id.bytes(), textmode, + &accounts.mutefun(), ) .id_source(egui::Id::new(("threadscroll", col))) - .ui(ui, &accounts.mutefun()) + .ui(ui) .map(Into::into), TimelineRoute::Reply(id) => { @@ -173,9 +175,10 @@ pub fn render_profile_route( ndb, note_cache, img_cache, + is_muted, NoteOptions::default(), ) - .ui(ui, is_muted); + .ui(ui); if let Some(action) = action { match action { diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs @@ -503,7 +503,6 @@ pub fn render_add_column_routes( ctx.pool, ctx.note_cache, app.since_optimize, - &ctx.accounts.mutefun(), ctx.accounts .get_selected_account() .as_ref() diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs @@ -29,6 +29,7 @@ pub struct ProfileView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, img_cache: &'a mut ImageCache, + is_muted: &'a MuteFun, } pub enum ProfileViewAction { @@ -46,6 +47,7 @@ impl<'a> ProfileView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, img_cache: &'a mut ImageCache, + is_muted: &'a MuteFun, note_options: NoteOptions, ) -> Self { ProfileView { @@ -57,10 +59,11 @@ impl<'a> ProfileView<'a> { note_cache, img_cache, note_options, + is_muted, } } - pub fn ui(&mut self, ui: &mut egui::Ui, is_muted: &MuteFun) -> Option<ProfileViewAction> { + pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<ProfileViewAction> { let scroll_id = egui::Id::new(("profile_scroll", self.col_id, self.pubkey)); ScrollArea::vertical() @@ -75,20 +78,14 @@ impl<'a> ProfileView<'a> { } let profile = self .profiles - .notes_holder_mutated( - self.ndb, - self.note_cache, - &txn, - self.pubkey.bytes(), - is_muted, - ) + .notes_holder_mutated(self.ndb, self.note_cache, &txn, self.pubkey.bytes()) .get_ptr(); profile.timeline.selected_view = tabs_ui(ui, profile.timeline.selected_view, &profile.timeline.views); // poll for new notes and insert them into our existing notes - if let Err(e) = profile.poll_notes_into_view(&txn, self.ndb, is_muted) { + if let Err(e) = profile.poll_notes_into_view(&txn, self.ndb) { error!("Profile::poll_notes_into_view: {e}"); } @@ -102,11 +99,13 @@ impl<'a> ProfileView<'a> { self.ndb, self.note_cache, self.img_cache, + self.is_muted, ) .show(ui) { action = Some(ProfileViewAction::Note(note_action)); } + action }) .inner diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs @@ -20,6 +20,7 @@ pub struct ThreadView<'a> { selected_note_id: &'a [u8; 32], textmode: bool, id_source: egui::Id, + is_muted: &'a MuteFun, } impl<'a> ThreadView<'a> { @@ -32,6 +33,7 @@ impl<'a> ThreadView<'a> { img_cache: &'a mut ImageCache, selected_note_id: &'a [u8; 32], textmode: bool, + is_muted: &'a MuteFun, ) -> Self { let id_source = egui::Id::new("threadscroll_threadview"); ThreadView { @@ -43,6 +45,7 @@ impl<'a> ThreadView<'a> { selected_note_id, textmode, id_source, + is_muted, } } @@ -51,7 +54,7 @@ impl<'a> ThreadView<'a> { self } - pub fn ui(&mut self, ui: &mut egui::Ui, is_muted: &MuteFun) -> Option<NoteAction> { + pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> { let txn = Transaction::new(self.ndb).expect("txn"); let selected_note_key = @@ -93,13 +96,13 @@ impl<'a> ThreadView<'a> { let thread = self .threads - .notes_holder_mutated(self.ndb, self.note_cache, &txn, root_id, is_muted) + .notes_holder_mutated(self.ndb, self.note_cache, &txn, root_id) .get_ptr(); // TODO(jb55): skip poll if ThreadResult is fresh? // poll for new notes and insert them into our existing notes - match thread.poll_notes_into_view(&txn, self.ndb, is_muted) { + match thread.poll_notes_into_view(&txn, self.ndb) { Ok(action) => { action.process_action(&txn, self.ndb, self.unknown_ids, self.note_cache) } @@ -119,6 +122,7 @@ impl<'a> ThreadView<'a> { self.ndb, self.note_cache, self.img_cache, + self.is_muted, ) .show(ui) }) diff --git a/crates/notedeck_columns/src/ui/timeline.rs b/crates/notedeck_columns/src/ui/timeline.rs @@ -7,10 +7,11 @@ use crate::{ ui::note::NoteOptions, }; use egui::containers::scroll_area::ScrollBarVisibility; -use egui::{Direction, Layout}; +use egui::{Color32, Direction, Layout}; use egui_tabs::TabColor; use nostrdb::{Ndb, Transaction}; -use notedeck::{ImageCache, NoteCache}; +use notedeck::note::root_note_id_from_selected_id; +use notedeck::{ImageCache, MuteFun, NoteCache}; use tracing::{error, warn}; pub struct TimelineView<'a> { @@ -21,6 +22,7 @@ pub struct TimelineView<'a> { img_cache: &'a mut ImageCache, note_options: NoteOptions, reverse: bool, + is_muted: &'a MuteFun, } impl<'a> TimelineView<'a> { @@ -31,6 +33,7 @@ impl<'a> TimelineView<'a> { note_cache: &'a mut NoteCache, img_cache: &'a mut ImageCache, note_options: NoteOptions, + is_muted: &'a MuteFun, ) -> TimelineView<'a> { let reverse = false; TimelineView { @@ -41,6 +44,7 @@ impl<'a> TimelineView<'a> { img_cache, reverse, note_options, + is_muted, } } @@ -54,6 +58,7 @@ impl<'a> TimelineView<'a> { self.img_cache, self.reverse, self.note_options, + self.is_muted, ) } @@ -73,6 +78,7 @@ fn timeline_ui( img_cache: &mut ImageCache, reversed: bool, note_options: NoteOptions, + is_muted: &MuteFun, ) -> Option<NoteAction> { //padding(4.0, ui, |ui| ui.heading("Notifications")); /* @@ -123,6 +129,7 @@ fn timeline_ui( ndb, note_cache, img_cache, + is_muted, ) .show(ui) }) @@ -224,9 +231,11 @@ pub struct TimelineTabView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, img_cache: &'a mut ImageCache, + is_muted: &'a MuteFun, } impl<'a> TimelineTabView<'a> { + #[allow(clippy::too_many_arguments)] pub fn new( tab: &'a TimelineTab, reversed: bool, @@ -235,6 +244,7 @@ impl<'a> TimelineTabView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, img_cache: &'a mut ImageCache, + is_muted: &'a MuteFun, ) -> Self { Self { tab, @@ -244,6 +254,7 @@ impl<'a> TimelineTabView<'a> { ndb, note_cache, img_cache, + is_muted, } } @@ -251,6 +262,7 @@ impl<'a> TimelineTabView<'a> { let mut action: Option<NoteAction> = None; let len = self.tab.notes.len(); + let is_muted = self.is_muted; self.tab .list .clone() @@ -275,17 +287,30 @@ impl<'a> TimelineTabView<'a> { }; ui::padding(8.0, ui, |ui| { - let resp = ui::NoteView::new(self.ndb, self.note_cache, self.img_cache, &note) - .note_options(self.note_options) - .show(ui); - - if let Some(note_action) = resp.action { - action = Some(note_action) - } - - if let Some(context) = resp.context_selection { - context.process(ui, &note); - } + if let Some(muted_reason) = is_muted( + &note, + root_note_id_from_selected_id( + self.ndb, + self.note_cache, + self.txn, + note.id(), + ), + ) { + ui.colored_label(Color32::RED, format!("MUTED {}", muted_reason)); + } else { + let resp = + ui::NoteView::new(self.ndb, self.note_cache, self.img_cache, &note) + .note_options(self.note_options) + .show(ui); + + if let Some(note_action) = resp.action { + action = Some(note_action) + } + + if let Some(context) = resp.context_selection { + context.process(ui, &note); + } + }; }); ui::hline(ui);