diff --git a/Cargo.lock b/Cargo.lock index 42d3f51..526422c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -994,7 +994,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -1123,7 +1123,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1564,7 +1564,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "proc-macro2", "quote", @@ -2490,7 +2490,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2596,7 +2596,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "anyhow", "gpui", @@ -2819,7 +2819,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "anyhow", "bytes", @@ -2839,7 +2839,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3397,9 +3397,9 @@ dependencies = [ [[package]] name = "lebe" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" @@ -3636,7 +3636,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "anyhow", "bindgen 0.71.1", @@ -3884,7 +3884,7 @@ dependencies = [ [[package]] name = "nostr" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951" +source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38" dependencies = [ "aes", "base64", @@ -3908,7 +3908,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951" +source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38" dependencies = [ "async-utility", "nostr", @@ -3920,7 +3920,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951" +source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38" dependencies = [ "flatbuffers", "lru", @@ -3931,7 +3931,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951" +source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38" dependencies = [ "async-utility", "flume", @@ -3945,7 +3945,7 @@ dependencies = [ [[package]] name = "nostr-relay-pool" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951" +source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38" dependencies = [ "async-utility", "async-wsocket", @@ -3962,7 +3962,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.43.0" -source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951" +source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38" dependencies = [ "async-utility", "nostr", @@ -4751,9 +4751,9 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "376f733579ac4d3b9fbf0afca99bf8f6b698d541118affca554d0b86f73c2470" +checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84" dependencies = [ "num-traits", ] @@ -5076,7 +5076,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "derive_refineable", "workspace-hack", @@ -5230,7 +5230,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "anyhow", "bytes", @@ -5765,7 +5765,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "anyhow", "serde", @@ -6206,7 +6206,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "arrayvec", "log", @@ -7236,7 +7236,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d" +source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918" dependencies = [ "anyhow", "async-fs", @@ -7707,11 +7707,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -7739,7 +7739,7 @@ dependencies = [ "windows-collections", "windows-core 0.61.2", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -7785,7 +7785,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", ] @@ -7797,7 +7797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -7851,6 +7851,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -7858,7 +7864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -7878,7 +7884,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", ] @@ -7898,7 +7904,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -7907,7 +7913,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -7916,7 +7922,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -7964,6 +7970,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -8016,7 +8031,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -8033,7 +8048,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8492,18 +8507,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index c1d4b26..0d706c6 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -221,12 +221,12 @@ impl ChatSpace { let nip65 = Filter::new().kind(Kind::RelayList).author(public_key); if client.database().count(nip65).await.unwrap_or(0) > 0 { - let nip17 = Filter::new().kind(Kind::InboxRelays).author(public_key); + let dm_relays = Filter::new().kind(Kind::InboxRelays).author(public_key); - match client.database().query(nip17).await { + match client.database().query(dm_relays).await { Ok(events) => { if let Some(event) = events.first_owned() { - let relay_urls = Self::extract_relay_list(&event); + let relay_urls = nip17::extract_relay_list(&event).collect_vec(); if relay_urls.is_empty() { if !is_sent_signal { @@ -374,9 +374,6 @@ impl ChatSpace { let ingester = ingester(); let css = css(); - let auto_close = - SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); - let mut processed_events: HashSet = HashSet::new(); let mut challenges: HashSet> = HashSet::new(); let mut notifications = client.notifications(); @@ -395,7 +392,6 @@ impl ChatSpace { match event.kind { Kind::RelayList => { - // Get metadata for event's pubkey that matches the current user's pubkey if let Ok(true) = Self::is_self_event(&event).await { // Fetch user's metadata event Self::fetch_single_event(Kind::Metadata, event.pubkey).await; @@ -409,10 +405,10 @@ impl ChatSpace { } Kind::InboxRelays => { if let Ok(true) = Self::is_self_event(&event).await { - let relays: Vec = Self::extract_relay_list(&event); + let relays = nip17::extract_relay_list(&event).collect_vec(); if !relays.is_empty() { - for relay in relays.iter() { + for relay in relays.clone().into_iter() { if client.add_relay(relay).await.is_err() { let notice = Notice::RelayFailed(relay.clone()); ingester.send(Signal::Notice(notice)).await; @@ -424,7 +420,7 @@ impl ChatSpace { } // Subscribe to gift wrap events only in the current user's NIP-17 relays - Self::fetch_gift_wrap(&relays, event.pubkey).await; + Self::fetch_gift_wrap(relays, event.pubkey).await; } } } @@ -437,7 +433,7 @@ impl ChatSpace { Filter::new().limit(limit).authors(public_keys).kinds(kinds); client - .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close)) + .subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts) .await .ok(); } @@ -587,20 +583,6 @@ impl ChatSpace { } } - fn extract_relay_list(event: &Event) -> Vec { - event - .tags - .filter_standardized(TagKind::Relay) - .filter_map(|t| { - if let TagStandard::Relay(url) = t { - Some(url.to_owned()) - } else { - None - } - }) - .collect() - } - /// Checks if an event is belong to the current user async fn is_self_event(event: &Event) -> Result { let client = nostr_client(); @@ -613,22 +595,21 @@ impl ChatSpace { /// Fetches a single event by kind and public key pub async fn fetch_single_event(kind: Kind, public_key: PublicKey) { let client = nostr_client(); - let auto_close = - SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + let css = css(); let filter = Filter::new().kind(kind).author(public_key).limit(1); - if let Err(e) = client.subscribe(filter, Some(auto_close)).await { + if let Err(e) = client.subscribe(filter, css.auto_close_opts).await { log::info!("Failed to subscribe: {e}"); } } - pub async fn fetch_gift_wrap(relays: &[RelayUrl], public_key: PublicKey) { + pub async fn fetch_gift_wrap(relays: Vec<&RelayUrl>, public_key: PublicKey) { let client = nostr_client(); let sub_id = css().gift_wrap_sub_id.clone(); let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); if client - .subscribe_with_id_to(relays.to_owned(), sub_id, filter, None) + .subscribe_with_id_to(relays.clone(), sub_id, filter, None) .await .is_ok() { @@ -639,8 +620,7 @@ impl ChatSpace { /// Fetches NIP-65 relay list for a given public key pub async fn fetch_nip65_relays(public_key: PublicKey) -> Result<(), Error> { let client = nostr_client(); - let auto_close = - SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + let css = css(); let filter = Filter::new() .kind(Kind::RelayList) @@ -648,7 +628,7 @@ impl ChatSpace { .limit(1); client - .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close)) + .subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts) .await?; Ok(()) @@ -656,18 +636,15 @@ impl ChatSpace { /// Fetches metadata for a list of public keys async fn fetch_metadata_for_pubkeys(public_keys: HashSet) { - if public_keys.is_empty() { - return; - }; - let client = nostr_client(); - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); - let kinds = vec![Kind::Metadata, Kind::ContactList]; - let limit = public_keys.len() * kinds.len(); + let css = css(); + + let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList]; + let limit = public_keys.len() * kinds.len() + 20; // + 20 to ensure Coop has enough metadata let filter = Filter::new().limit(limit).authors(public_keys).kinds(kinds); client - .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) + .subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts) .await .ok(); } diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/views/screening.rs index 747dcfb..06e19b1 100644 --- a/crates/coop/src/views/screening.rs +++ b/crates/coop/src/views/screening.rs @@ -1,9 +1,13 @@ +use std::time::Duration; + use common::display::{shorten_pubkey, ReadableProfile}; use common::nip05::nip05_verify; +use global::constants::BOOTSTRAP_RELAYS; use global::nostr_client; +use gpui::prelude::FluentBuilder; use gpui::{ - div, relative, rems, App, AppContext, Context, Div, Entity, IntoElement, ParentElement, Render, - SharedString, Styled, Task, Window, + div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity, + InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, }; use gpui_tokio::Tokio; use i18n::{shared_t, t}; @@ -14,6 +18,7 @@ use smallvec::{smallvec, SmallVec}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonRounded, ButtonVariants}; +use ui::indicator::Indicator; use ui::{h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt}; pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity { @@ -24,9 +29,10 @@ pub struct Screening { profile: Profile, verified: bool, followed: bool, - dm_relays: bool, - mutual_contacts: usize, - _tasks: SmallVec<[Task<()>; 1]>, + dm_relays: Option, + active: Option, + mutual_contacts: Vec, + _tasks: SmallVec<[Task<()>; 4]>, } impl Screening { @@ -37,33 +43,69 @@ impl Screening { let mut tasks = smallvec![]; - let check_trust_score: Task<(bool, usize, bool)> = cx.background_spawn(async move { + let contact_check: Task<(bool, Vec)> = cx.background_spawn(async move { let client = nostr_client(); - let follow = Filter::new() - .kind(Kind::ContactList) - .author(identity) - .pubkey(public_key) + // Check if user is in contact list + let contacts = client.database().contacts_public_keys(identity).await; + let followed = contacts.unwrap_or_default().contains(&public_key); + + // Check mutual contacts + let contact_list = Filter::new().kind(Kind::ContactList).pubkey(public_key); + let mut mutual_contacts = vec![]; + + if let Ok(events) = client.database().query(contact_list).await { + for event in events.into_iter().filter(|ev| ev.pubkey != identity) { + if let Ok(metadata) = client.database().metadata(event.pubkey).await { + let profile = Profile::new(event.pubkey, metadata.unwrap_or_default()); + mutual_contacts.push(profile); + } + } + } + + (followed, mutual_contacts) + }); + + let activity_check = cx.background_spawn(async move { + let client = nostr_client(); + let mut activity = false; + + let filter = Filter::new() + .author(public_key) + .since(Timestamp::now() - Duration::from_secs(172800)) .limit(1); - let contacts = Filter::new() - .kind(Kind::ContactList) - .pubkey(public_key) - .limit(1); + if let Ok(mut stream) = client + .stream_events_from(BOOTSTRAP_RELAYS, filter, Duration::from_secs(2)) + .await + { + while stream.next().await.is_some() { + activity = true + } + } - let relays = Filter::new() + activity + }); + + let relay_check = cx.background_spawn(async move { + let client = nostr_client(); + let mut relay = false; + + let filter = Filter::new() .kind(Kind::InboxRelays) .author(public_key) .limit(1); - let is_follow = client.database().count(follow).await.unwrap_or(0) >= 1; - let mutual_contacts = client.database().count(contacts).await.unwrap_or(0); - let dm_relays = client.database().count(relays).await.unwrap_or(0) >= 1; + if let Ok(mut stream) = client.stream_events(filter, Duration::from_secs(2)).await { + while stream.next().await.is_some() { + relay = true + } + } - (is_follow, mutual_contacts, dm_relays) + relay }); - let verify_nip05 = if let Some(address) = profile.metadata().nip05 { + let addr_check = if let Some(address) = profile.metadata().nip05 { Some(Tokio::spawn(cx, async move { nip05_verify(public_key, &address).await.unwrap_or(false) })) @@ -72,20 +114,49 @@ impl Screening { }; tasks.push( - // Load all necessary data + // Run the contact check in the background cx.spawn_in(window, async move |this, cx| { - let (followed, mutual_contacts, dm_relays) = check_trust_score.await; + let (followed, mutual_contacts) = contact_check.await; this.update(cx, |this, cx| { this.followed = followed; this.mutual_contacts = mutual_contacts; - this.dm_relays = dm_relays; cx.notify(); }) .ok(); + }), + ); - // Update the NIP05 verification status if user has NIP05 address - if let Some(task) = verify_nip05 { + tasks.push( + // Run the activity check in the background + cx.spawn_in(window, async move |this, cx| { + let active = activity_check.await; + + this.update(cx, |this, cx| { + this.active = Some(active); + cx.notify(); + }) + .ok(); + }), + ); + + tasks.push( + // Run the relay check in the background + cx.spawn_in(window, async move |this, cx| { + let relay = relay_check.await; + + this.update(cx, |this, cx| { + this.dm_relays = Some(relay); + cx.notify(); + }) + .ok(); + }), + ); + + tasks.push( + // Run the NIP-05 verification in the background + cx.spawn_in(window, async move |this, cx| { + if let Some(task) = addr_check { if let Ok(verified) = task.await { this.update(cx, |this, cx| { this.verified = verified; @@ -101,8 +172,9 @@ impl Screening { profile, verified: false, followed: false, - dm_relays: false, - mutual_contacts: 0, + dm_relays: None, + active: None, + mutual_contacts: vec![], _tasks: tasks, } } @@ -141,12 +213,54 @@ impl Screening { }) .detach(); } + + fn mutual_contacts(&mut self, window: &mut Window, cx: &mut Context) { + let contacts = self.mutual_contacts.clone(); + + window.open_modal(cx, move |this, _window, _cx| { + let contacts = contacts.clone(); + let total = contacts.len(); + + this.title(shared_t!("screening.mutual_label")).child( + v_flex().gap_1().pb_4().child( + uniform_list("contacts", total, move |range, _window, cx| { + let mut items = Vec::with_capacity(total); + + for ix in range { + if let Some(contact) = contacts.get(ix) { + items.push( + h_flex() + .h_11() + .w_full() + .px_2() + .gap_1p5() + .rounded(cx.theme().radius) + .text_sm() + .hover(|this| { + this.bg(cx.theme().elevated_surface_background) + }) + .child( + Avatar::new(contact.avatar_url(true)).size(rems(1.75)), + ) + .child(contact.display_name()), + ); + } + } + + items + }) + .h(px(300.)), + ), + ) + }); + } } impl Render for Screening { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let proxy = AppSettings::get_proxy_user_avatars(cx); let shorten_pubkey = shorten_pubkey(self.profile.public_key(), 8); + let total_mutuals = self.mutual_contacts.len(); v_flex() .gap_4() @@ -168,12 +282,10 @@ impl Render for Screening { h_flex() .gap_3() .child( - div() + h_flex() .p_1() .flex_1() .h_7() - .flex() - .items_center() .justify_center() .rounded_full() .bg(cx.theme().surface_background) @@ -217,25 +329,54 @@ impl Render for Screening { .items_start() .gap_2() .text_sm() - .child(status_badge(self.followed, cx)) + .child(status_badge(Some(self.followed), cx)) .child( v_flex() .text_sm() .child(shared_t!("screening.contact_label")) - .child(div().text_color(cx.theme().text_muted).child({ - if self.followed { - shared_t!("screening.contact") - } else { - shared_t!("screening.not_contact") - } - })), + .child( + div() + .line_clamp(1) + .text_color(cx.theme().text_muted) + .child({ + if self.followed { + shared_t!("screening.contact") + } else { + shared_t!("screening.not_contact") + } + }), + ), ), ) .child( h_flex() .items_start() .gap_2() - .child(status_badge(self.verified, cx)) + .text_sm() + .child(status_badge(self.active, cx)) + .child( + v_flex() + .text_sm() + .child(shared_t!("screening.active_label")) + .child( + div() + .line_clamp(1) + .text_color(cx.theme().text_muted) + .child({ + if self.active == Some(true) { + shared_t!("screening.active") + } else { + shared_t!("screening.no_active") + } + }), + ), + ), + ) + .child( + h_flex() + .items_start() + .gap_2() + .child(status_badge(Some(self.verified), cx)) .child( v_flex() .text_sm() @@ -246,35 +387,61 @@ impl Render for Screening { shared_t!("screening.nip05_label") } }) - .child(div().text_color(cx.theme().text_muted).child({ - if self.address(cx).is_some() { - if self.verified { - shared_t!("screening.nip05_ok") - } else { - shared_t!("screening.nip05_failed") - } - } else { - shared_t!("screening.nip05_empty") - } - })), + .child( + div() + .line_clamp(1) + .text_color(cx.theme().text_muted) + .child({ + if self.address(cx).is_some() { + if self.verified { + shared_t!("screening.nip05_ok") + } else { + shared_t!("screening.nip05_failed") + } + } else { + shared_t!("screening.nip05_empty") + } + }), + ), ), ) .child( h_flex() .items_start() .gap_2() - .child(status_badge(self.mutual_contacts > 0, cx)) + .child(status_badge(Some(total_mutuals > 0), cx)) .child( v_flex() .text_sm() - .child(shared_t!("screening.mutual_label")) - .child(div().text_color(cx.theme().text_muted).child({ - if self.mutual_contacts > 0 { - shared_t!("screening.mutual", u = self.mutual_contacts) - } else { - shared_t!("screening.no_mutual") - } - })), + .child( + h_flex() + .gap_0p5() + .child(shared_t!("screening.mutual_label")) + .child( + Button::new("mutuals") + .icon(IconName::Info) + .xsmall() + .ghost() + .rounded(ButtonRounded::Full) + .on_click(cx.listener( + move |this, _, window, cx| { + this.mutual_contacts(window, cx); + }, + )), + ), + ) + .child( + div() + .line_clamp(1) + .text_color(cx.theme().text_muted) + .child({ + if total_mutuals > 0 { + shared_t!("screening.mutual", u = total_mutuals) + } else { + shared_t!("screening.no_mutual") + } + }), + ), ), ) .child( @@ -287,36 +454,43 @@ impl Render for Screening { .w_full() .text_sm() .child({ - if self.dm_relays { + if self.dm_relays == Some(true) { shared_t!("screening.relay_found") } else { shared_t!("screening.relay_empty") } }) - .child(div().w_full().text_color(cx.theme().text_muted).child( - { - if self.dm_relays { - shared_t!("screening.relay_found_desc") - } else { - shared_t!("screening.relay_empty_desc") - } - }, - )), + .child( + div() + .w_full() + .line_clamp(1) + .text_color(cx.theme().text_muted) + .child({ + if self.dm_relays == Some(true) { + shared_t!("screening.relay_found_desc") + } else { + shared_t!("screening.relay_empty_desc") + } + }), + ), ), ), ) } } -fn status_badge(status: bool, cx: &App) -> Div { - div() - .pt_1() - .flex_shrink_0() - .child(Icon::new(IconName::CheckCircleFill).small().text_color({ - if status { - cx.theme().icon_accent - } else { - cx.theme().icon_muted - } - })) +fn status_badge(status: Option, cx: &App) -> Div { + div().pt_1().flex_shrink_0().map(|this| { + if let Some(status) = status { + this.child(Icon::new(IconName::CheckCircleFill).small().text_color({ + if status { + cx.theme().icon_accent + } else { + cx.theme().icon_muted + } + })) + } else { + this.child(Indicator::new().xsmall()) + } + }) } diff --git a/crates/coop/src/views/setup_relay.rs b/crates/coop/src/views/setup_relay.rs index 586e0dd..b8ccb15 100644 --- a/crates/coop/src/views/setup_relay.rs +++ b/crates/coop/src/views/setup_relay.rs @@ -2,7 +2,7 @@ use std::time::Duration; use anyhow::{anyhow, Error}; use global::constants::NIP17_RELAYS; -use global::nostr_client; +use global::{css, nostr_client}; use gpui::prelude::FluentBuilder; use gpui::{ div, px, uniform_list, App, AppContext, Context, Entity, InteractiveElement, IntoElement, @@ -19,8 +19,6 @@ use ui::input::{InputEvent, InputState, TextInput}; use ui::modal::ModalButtonProps; use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, StyledExt}; -use crate::chatspace::ChatSpace; - pub fn init(kind: Kind, window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| SetupRelay::new(kind, window, cx)) } @@ -220,7 +218,16 @@ impl SetupRelay { } // Fetch gift wrap events - ChatSpace::fetch_gift_wrap(&relays, public_key).await; + let sub_id = css().gift_wrap_sub_id.clone(); + let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); + + if client + .subscribe_with_id_to(relays.clone(), sub_id, filter, None) + .await + .is_ok() + { + log::info!("Subscribed to messages in: {relays:?}"); + }; Ok(()) }); diff --git a/crates/global/src/lib.rs b/crates/global/src/lib.rs index 691b5c7..f75aeaa 100644 --- a/crates/global/src/lib.rs +++ b/crates/global/src/lib.rs @@ -119,6 +119,7 @@ pub struct CoopSimpleStorage { pub init_at: Timestamp, pub gift_wrap_sub_id: SubscriptionId, pub gift_wrap_processing: AtomicBool, + pub auto_close_opts: Option, pub sent_ids: RwLock>, pub resent_ids: RwLock>>, pub resend_queue: RwLock>, @@ -136,6 +137,9 @@ impl CoopSimpleStorage { init_at: Timestamp::now(), gift_wrap_sub_id: SubscriptionId::new("inbox"), gift_wrap_processing: AtomicBool::new(false), + auto_close_opts: Some( + SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE), + ), sent_ids: RwLock::new(HashSet::new()), resent_ids: RwLock::new(Vec::new()), resend_queue: RwLock::new(HashMap::new()), diff --git a/locales/app.yml b/locales/app.yml index 23b61a1..9ff0ae3 100644 --- a/locales/app.yml +++ b/locales/app.yml @@ -219,6 +219,12 @@ screening: en: "This person is one of your contacts." not_contact: en: "This person is not one of your contacts." + active_label: + en: "Activity" + active: + en: "This person has activity in the last 2 days" + no_active: + en: "This person has no activity in the last 2 days" mutual_label: en: "Mutual contacts" mutual: