diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 12bd9bc..6c90ea0 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -15,6 +15,7 @@ use gpui::{ }; use nostr_sdk::prelude::*; use smallvec::{SmallVec, smallvec}; +use smol::lock::RwLock; use state::{DEVICE_GIFTWRAP, NostrRegistry, StateEvent, TIMEOUT, USER_GIFTWRAP}; mod message; @@ -60,9 +61,12 @@ enum Signal { /// Chat Registry #[derive(Debug)] pub struct ChatRegistry { - /// Collection of all chat rooms + /// Chat rooms rooms: Vec>, + /// Tracking events seen on which relays in the current session + seens: Arc>>>, + /// Tracking the status of unwrapping gift wrap events. tracking_flag: Arc, @@ -119,6 +123,7 @@ impl ChatRegistry { Self { rooms: vec![], + seens: Arc::new(RwLock::new(HashMap::default())), tracking_flag: Arc::new(AtomicBool::new(false)), signal_rx: rx, signal_tx: tx, @@ -133,6 +138,7 @@ impl ChatRegistry { let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); let status = self.tracking_flag.clone(); + let seens = self.seens.clone(); let initialized_at = Timestamp::now(); let sub_id1 = SubscriptionId::new(DEVICE_GIFTWRAP); @@ -148,20 +154,26 @@ impl ChatRegistry { let mut processed_events = HashSet::new(); while let Some(notification) = notifications.next().await { - let ClientNotification::Message { message, .. } = notification else { + let ClientNotification::Message { message, relay_url } = notification else { // Skip non-message notifications continue; }; match message { RelayMessage::Event { event, .. } => { + // Keep track of which relays have seen this event + { + let mut seens = seens.write().await; + seens.entry(event.id).or_default().insert(relay_url); + } + + // De-duplicate events by their ID if !processed_events.insert(event.id) { - // Skip if the event has already been processed continue; } + // Skip non-gift wrap events if event.kind != Kind::GiftWrap { - // Skip non-gift wrap events continue; } @@ -169,26 +181,21 @@ impl ChatRegistry { match extract_rumor(&client, &device_signer, event.as_ref()).await { Ok(rumor) => { if rumor.tags.is_empty() { - let error: SharedString = - "Message doesn't belong to any rooms".into(); + let error: SharedString = "No room for message".into(); tx.send_async(Signal::Error(error)).await?; } - match rumor.created_at >= initialized_at { - true => { - let new_message = NewMessage::new(event.id, rumor); - let signal = Signal::Message(new_message); + if rumor.created_at >= initialized_at { + let new_message = NewMessage::new(event.id, rumor); + let signal = Signal::Message(new_message); - tx.send_async(signal).await?; - } - false => { - status.store(true, Ordering::Release); - } + tx.send_async(signal).await?; + } else { + status.store(true, Ordering::Release); } } Err(e) => { - let error: SharedString = - format!("Failed to unwrap the gift wrap event: {e}").into(); + let error: SharedString = format!("Failed to unwrap: {e}").into(); tx.send_async(Signal::Error(error)).await?; } } @@ -399,6 +406,24 @@ impl ChatRegistry { .count() } + /// Count the number of messages seen by a given relay. + pub fn count_messages(&self, relay_url: &RelayUrl) -> usize { + self.seens + .read_blocking() + .values() + .filter(|seen| seen.contains(relay_url)) + .count() + } + + /// Get the relays that have seen a given message. + pub fn seen_on(&self, id: &EventId) -> HashSet { + self.seens + .read_blocking() + .get(id) + .cloned() + .unwrap_or_default() + } + /// Add a new room to the start of list. pub fn add_room(&mut self, room: I, cx: &mut Context) where diff --git a/crates/coop/src/workspace.rs b/crates/coop/src/workspace.rs index 6f82f8f..3bcff2e 100644 --- a/crates/coop/src/workspace.rs +++ b/crates/coop/src/workspace.rs @@ -739,28 +739,49 @@ impl Workspace { }) .when(inbox_connected, |this| this.indicator()) .dropdown_menu(move |this, _window, cx| { + let chat = ChatRegistry::global(cx); let persons = PersonRegistry::global(cx); let profile = persons.read(cx).get(&public_key, cx); - let urls: Vec = profile + let urls: Vec<(SharedString, SharedString)> = profile .messaging_relays() .iter() - .map(|url| SharedString::from(url.to_string())) + .map(|url| { + ( + SharedString::from(url.to_string()), + chat.read(cx).count_messages(url).to_string().into(), + ) + }) .collect(); // Header let menu = this.min_w(px(260.)).label("Messaging Relays"); // Content - let menu = urls.into_iter().fold(menu, |this, url| { - this.item(PopupMenuItem::element(move |_window, _cx| { + let menu = urls.into_iter().fold(menu, |this, (url, count)| { + this.item(PopupMenuItem::element(move |_window, cx| { h_flex() .px_1() .w_full() - .gap_2() .text_sm() - .child(div().size_1p5().rounded_full().bg(gpui::green())) - .child(url.clone()) + .justify_between() + .child( + h_flex() + .gap_2() + .child( + div() + .size_1p5() + .rounded_full() + .bg(cx.theme().icon_accent), + ) + .child(url.clone()), + ) + .child( + div() + .text_xs() + .text_color(cx.theme().text_muted) + .child(count.clone()), + ) })) }); diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 5a7d322..4702e96 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -116,7 +116,7 @@ impl NostrRegistry { .gossip(gossip) .automatic_authentication(false) .verify_subscriptions(false) - .connect_timeout(Duration::from_secs(TIMEOUT)) + .connect_timeout(Duration::from_secs(10)) .sleep_when_idle(SleepWhenIdle::Enabled { timeout: Duration::from_secs(600), }); diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index b7de303..b280e1a 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -339,11 +339,12 @@ impl Render for Notification { .when(only_message, |this| this.items_center()) .refine_style(&self.style) .when_some(icon, |this, icon| { - this.child(div().flex_shrink_0().child(icon)) + this.child(div().flex_shrink_0().size_5().child(icon)) }) .child( v_flex() .flex_1() + .gap_1() .overflow_hidden() .when_some(self.title.clone(), |this, title| { this.child(h_flex().h_5().text_sm().font_semibold().child(title)) @@ -352,9 +353,7 @@ impl Render for Notification { this.child( div() .text_sm() - .when(has_title, |this| { - this.mt_2().text_color(cx.theme().text_muted) - }) + .when(has_title, |this| this.text_color(cx.theme().text_muted)) .line_height(relative(1.3)) .child(message), ) @@ -363,7 +362,6 @@ impl Render for Notification { .when_some(action, |this, action| { this.child( h_flex() - .mt_2() .w_full() .flex_1() .gap_1()