add contact-based community suggestions

This commit is contained in:
2026-10-07 08:11:57 +07:00
parent 33785f183d
commit c058270eff
3 changed files with 674 additions and 27 deletions
+124 -9
View File
@@ -35,6 +35,10 @@ pub struct BrowsePanel {
relay: Option<RelayUrl>,
/// The groups the relay has announced so far
candidates: Vec<GroupCandidate>,
/// The communities of the user's contacts
contacts: Vec<GroupCandidate>,
/// Whether the contacts' communities are still resolving
contacts_loading: bool,
/// Whether the relay is still answering
loading: bool,
_subscriptions: SmallVec<[Subscription; 2]>,
@@ -63,21 +67,71 @@ impl BrowsePanel {
GroupsEvent::BrowseSettled(relay) => {
this.settle(relay.clone(), cx);
}
GroupsEvent::Suggested(candidate) => {
this.push_contact(candidate.as_ref().clone(), cx);
}
GroupsEvent::SuggestSettled => {
this.settle_contacts(cx);
}
_ => {}
}),
);
// Wait for the panel to leave the stack before kicking off the sync.
cx.defer_in(window, |this, _window, cx| {
this.load_contacts(cx);
});
Self {
focus_handle: cx.focus_handle(),
dock,
input,
relay: None,
candidates: Vec::new(),
contacts: Vec::new(),
contacts_loading: true,
loading: false,
_subscriptions: subscriptions,
}
}
/// Pull the resolved contacts' communities and start the contacts sync.
fn load_contacts(&mut self, cx: &mut Context<Self>) {
let registry = GroupsRegistry::global(cx);
let (candidates, loading) = registry.update(cx, |registry, cx| {
registry.load_contacts_groups(cx);
(
registry.contacts_candidates().to_vec(),
registry.contacts_loading(),
)
});
self.contacts = candidates;
self.contacts_loading = loading;
cx.notify();
}
fn push_contact(&mut self, candidate: GroupCandidate, cx: &mut Context<Self>) {
match self.contacts.iter_mut().find(|c| c.key == candidate.key) {
Some(existing) => *existing = candidate,
None => self.contacts.push(candidate),
}
self.contacts.sort_by(|a, b| {
a.display_name()
.to_lowercase()
.cmp(&b.display_name().to_lowercase())
});
cx.notify();
}
fn settle_contacts(&mut self, cx: &mut Context<Self>) {
self.contacts_loading = false;
cx.notify();
}
/// Browse the relay in the input.
fn browse(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let registry = GroupsRegistry::global(cx);
@@ -150,7 +204,13 @@ impl BrowsePanel {
);
}
fn render_rows(&self, range: Range<usize>, cx: &mut Context<Self>) -> Vec<AnyElement> {
fn render_rows(
&self,
candidates: &[GroupCandidate],
relay_detail: bool,
range: Range<usize>,
cx: &mut Context<Self>,
) -> Vec<AnyElement> {
let hide_avatar = AppSettings::get_hide_avatar(cx);
let panel = cx.entity().downgrade();
@@ -162,7 +222,7 @@ impl BrowsePanel {
.map(|group| group.read(cx).key().clone())
.collect();
self.candidates
candidates
.get(range)
.into_iter()
.flatten()
@@ -173,6 +233,11 @@ impl BrowsePanel {
.display_image()
.map(|picture| SharedString::from(picture.to_owned()));
let seed = key.cache_tag();
let detail = if relay_detail {
key.relay().domain().unwrap_or_else(|| key.relay().as_str())
} else {
key.id().as_str()
};
let is_joined = joined.contains(&key);
let action = if is_joined {
@@ -221,7 +286,7 @@ impl BrowsePanel {
.text_xs()
.text_color(cx.theme().text_placeholder)
.truncate()
.child(SharedString::from(key.id().as_str().to_owned())),
.child(SharedString::from(detail.to_owned())),
),
)
.child(action)
@@ -270,14 +335,53 @@ impl Focusable for BrowsePanel {
impl Render for BrowsePanel {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let count = self.candidates.len();
let from_contacts = self.relay.is_none();
let body = if self.relay.is_none() {
notice("Enter a relay to browse its groups.", cx)
let (contacts, contact_count) = if from_contacts {
let registry = GroupsRegistry::global(cx);
let joined: Vec<GroupKey> = registry
.read(cx)
.groups()
.iter()
.map(|group| group.read(cx).key().clone())
.collect();
let contacts: Vec<GroupCandidate> = self
.contacts
.iter()
.filter(|candidate| !joined.contains(&candidate.key))
.cloned()
.collect();
let count = contacts.len();
(contacts, count)
} else {
(Vec::new(), 0)
};
let body = if from_contacts {
if contact_count > 0 {
uniform_list(
"browse-contacts",
contact_count,
cx.processor(move |this, range, _window, cx| {
this.render_rows(&contacts, true, range, cx)
}),
)
.h_full()
.into_any_element()
} else if self.contacts_loading {
notice("Loading communities from your contacts…", cx)
} else {
notice("No communities from your contacts.", cx)
}
} else if count > 0 {
uniform_list(
"browse-groups",
count,
cx.processor(|this, range, _window, cx| this.render_rows(range, cx)),
cx.processor(|this, range, _window, cx| {
this.render_rows(&this.candidates, false, range, cx)
}),
)
.h_full()
.into_any_element()
@@ -303,10 +407,21 @@ impl Render for BrowsePanel {
this.browse(window, cx);
})),
)
.when(self.loading, |this| {
this.child(Indicator::new().small().color(cx.theme().icon_accent))
}),
.when(
self.loading || (from_contacts && self.contacts_loading),
|this| this.child(Indicator::new().small().color(cx.theme().icon_accent)),
),
)
.when(from_contacts && contact_count > 0, |this| {
this.child(
div()
.px_3()
.pb_1()
.text_xs()
.text_color(cx.theme().text_muted)
.child("From your contacts"),
)
})
.child(div().flex_1().min_h_0().child(body))
}
}