commit 7aca39aae8b894c7218ae758c72bd274591497c2
parent aa467b9be060c83d750f8804bf1b030d8abf6156
Author: kernelkind <kernelkind@gmail.com>
Date: Thu, 31 Jul 2025 18:52:28 -0400
add `NotesFreshness` to `TimelineTab`
necessary for notifications indicator
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
1 file changed, 106 insertions(+), 2 deletions(-)
diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs
@@ -8,6 +8,7 @@ use crate::{
use notedeck::{
contacts::hybrid_contacts_filter,
+ debouncer::Debouncer,
filter::{self, HybridFilter},
tr, Accounts, CachedNote, ContactState, FilterError, FilterState, FilterStates, Localization,
NoteCache, NoteRef, UnknownIds,
@@ -16,8 +17,11 @@ use notedeck::{
use egui_virtual_list::VirtualList;
use enostr::{PoolRelay, Pubkey, RelayPool};
use nostrdb::{Filter, Ndb, Note, NoteKey, Transaction};
-use std::cell::RefCell;
-use std::rc::Rc;
+use std::{
+ cell::RefCell,
+ time::{Duration, UNIX_EPOCH},
+};
+use std::{rc::Rc, time::SystemTime};
use tracing::{debug, error, info, warn};
@@ -103,6 +107,7 @@ pub struct TimelineTab {
pub selection: i32,
pub filter: ViewFilter,
pub list: Rc<RefCell<VirtualList>>,
+ pub freshness: NotesFreshness,
}
impl TimelineTab {
@@ -138,6 +143,7 @@ impl TimelineTab {
selection,
filter,
list,
+ freshness: NotesFreshness::default(),
}
}
@@ -780,3 +786,101 @@ pub fn is_timeline_ready(
}
}
}
+
+#[derive(Debug)]
+pub struct NotesFreshness {
+ debouncer: Debouncer,
+ state: NotesFreshnessState,
+}
+
+#[derive(Debug)]
+enum NotesFreshnessState {
+ Fresh {
+ timestamp_viewed: u64,
+ },
+ Stale {
+ have_unseen: bool,
+ timestamp_last_viewed: u64,
+ },
+}
+
+impl Default for NotesFreshness {
+ fn default() -> Self {
+ Self {
+ debouncer: Debouncer::new(Duration::from_secs(2)),
+ state: NotesFreshnessState::Stale {
+ have_unseen: true,
+ timestamp_last_viewed: 0,
+ },
+ }
+ }
+}
+
+impl NotesFreshness {
+ pub fn set_fresh(&mut self) {
+ if !self.debouncer.should_act() {
+ return;
+ }
+ self.state = NotesFreshnessState::Fresh {
+ timestamp_viewed: timestamp_now(),
+ };
+ self.debouncer.bounce();
+ }
+
+ pub fn update(&mut self, check_have_unseen: impl FnOnce(u64) -> bool) {
+ if !self.debouncer.should_act() {
+ return;
+ }
+
+ match &self.state {
+ NotesFreshnessState::Fresh { timestamp_viewed } => {
+ let Ok(dur) = SystemTime::now()
+ .duration_since(UNIX_EPOCH + Duration::from_secs(*timestamp_viewed))
+ else {
+ return;
+ };
+
+ if dur > Duration::from_secs(2) {
+ self.state = NotesFreshnessState::Stale {
+ have_unseen: check_have_unseen(*timestamp_viewed),
+ timestamp_last_viewed: *timestamp_viewed,
+ };
+ }
+ }
+ NotesFreshnessState::Stale {
+ have_unseen,
+ timestamp_last_viewed,
+ } => {
+ if *have_unseen {
+ return;
+ }
+
+ self.state = NotesFreshnessState::Stale {
+ have_unseen: check_have_unseen(*timestamp_last_viewed),
+ timestamp_last_viewed: *timestamp_last_viewed,
+ };
+ }
+ }
+
+ self.debouncer.bounce();
+ }
+
+ pub fn has_unseen(&self) -> bool {
+ match &self.state {
+ NotesFreshnessState::Fresh {
+ timestamp_viewed: _,
+ } => false,
+ NotesFreshnessState::Stale {
+ have_unseen,
+ timestamp_last_viewed: _,
+ } => *have_unseen,
+ }
+ }
+}
+
+fn timestamp_now() -> u64 {
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap_or(Duration::ZERO)
+ .as_secs()
+}