feat: rewrite the nip-4e implementation #1
@@ -4,11 +4,11 @@ use std::hash::{Hash, Hasher};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use common::{EventUtils, RenderedProfile};
|
||||
use common::EventUtils;
|
||||
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use person::{Person, PersonRegistry};
|
||||
use state::{tracker, NostrRegistry};
|
||||
|
||||
use crate::NewMessage;
|
||||
@@ -264,9 +264,9 @@ impl Room {
|
||||
}
|
||||
|
||||
/// Gets the display image for the room
|
||||
pub fn display_image(&self, proxy: bool, cx: &App) -> SharedString {
|
||||
pub fn display_image(&self, cx: &App) -> SharedString {
|
||||
if !self.is_group() {
|
||||
self.display_member(cx).avatar(proxy)
|
||||
self.display_member(cx).avatar()
|
||||
} else {
|
||||
SharedString::from("brand/group.png")
|
||||
}
|
||||
@@ -275,7 +275,7 @@ impl Room {
|
||||
/// Get a member to represent the room
|
||||
///
|
||||
/// Display member is always different from the current user.
|
||||
pub fn display_member(&self, cx: &App) -> Profile {
|
||||
pub fn display_member(&self, cx: &App) -> Person {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
||||
@@ -295,7 +295,7 @@ impl Room {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
|
||||
if self.is_group() {
|
||||
let profiles: Vec<Profile> = self
|
||||
let profiles: Vec<Person> = self
|
||||
.members
|
||||
.iter()
|
||||
.map(|public_key| persons.read(cx).get(public_key, cx))
|
||||
@@ -314,7 +314,7 @@ impl Room {
|
||||
|
||||
SharedString::from(name)
|
||||
} else {
|
||||
self.display_member(cx).display_name()
|
||||
self.display_member(cx).name()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
|
||||
pub use actions::*;
|
||||
use chat::{Message, RenderedMessage, Room, RoomEvent, RoomKind, SendReport};
|
||||
use common::{nip96_upload, RenderedProfile, RenderedTimestamp};
|
||||
use common::{nip96_upload, RenderedTimestamp};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, img, list, px, red, relative, rems, svg, white, AnyElement, App, AppContext,
|
||||
@@ -16,7 +16,7 @@ use gpui_tokio::Tokio;
|
||||
use indexset::{BTreeMap, BTreeSet};
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use person::{Person, PersonRegistry};
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smol::fs;
|
||||
@@ -506,7 +506,7 @@ impl ChatPanel {
|
||||
});
|
||||
}
|
||||
|
||||
fn profile(&self, public_key: &PublicKey, cx: &Context<Self>) -> Profile {
|
||||
fn profile(&self, public_key: &PublicKey, cx: &Context<Self>) -> Person {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
persons.read(cx).get(public_key, cx)
|
||||
}
|
||||
@@ -632,7 +632,7 @@ impl ChatPanel {
|
||||
this.child(
|
||||
div()
|
||||
.id(SharedString::from(format!("{ix}-avatar")))
|
||||
.child(Avatar::new(author.avatar(proxy)).size(rems(2.)))
|
||||
.child(Avatar::new(author.avatar()).size(rems(2.)))
|
||||
.context_menu(move |this, _window, _cx| {
|
||||
let view = Box::new(OpenPublicKey(public_key));
|
||||
let copy = Box::new(CopyPublicKey(public_key));
|
||||
@@ -657,7 +657,7 @@ impl ChatPanel {
|
||||
div()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text)
|
||||
.child(author.display_name()),
|
||||
.child(author.name()),
|
||||
)
|
||||
.child(message.created_at.to_human_time())
|
||||
.when_some(is_sent_success, |this, status| {
|
||||
@@ -714,7 +714,7 @@ impl ChatPanel {
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().text_accent)
|
||||
.child(author.display_name()),
|
||||
.child(author.name()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -796,8 +796,8 @@ impl ChatPanel {
|
||||
fn render_report(report: &SendReport, cx: &App) -> impl IntoElement {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get(&report.receiver, cx);
|
||||
let name = profile.display_name();
|
||||
let avatar = profile.avatar(true);
|
||||
let name = profile.name();
|
||||
let avatar = profile.avatar();
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
@@ -1080,7 +1080,7 @@ impl ChatPanel {
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().text_accent)
|
||||
.child(profile.display_name()),
|
||||
.child(profile.name()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -1134,7 +1134,7 @@ impl Panel for ChatPanel {
|
||||
.read_with(cx, |this, cx| {
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let label = this.display_name(cx);
|
||||
let url = this.display_image(proxy, cx);
|
||||
let url = this.display_image(cx);
|
||||
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::RenderedProfile;
|
||||
use gpui::{
|
||||
AnyElement, App, ElementId, HighlightStyle, InteractiveText, IntoElement, SharedString,
|
||||
StyledText, UnderlineStyle, Window,
|
||||
@@ -255,7 +254,7 @@ fn render_pubkey(
|
||||
) {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get(&public_key, cx);
|
||||
let display_name = format!("@{}", profile.display_name());
|
||||
let display_name = format!("@{}", profile.name());
|
||||
|
||||
text.replace_range(range.clone(), &display_name);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use auto_update::{AutoUpdateStatus, AutoUpdater};
|
||||
use chat::{ChatEvent, ChatRegistry};
|
||||
use chat_ui::{CopyPublicKey, OpenPublicKey};
|
||||
use common::{RenderedProfile, DEFAULT_SIDEBAR_WIDTH};
|
||||
use common::DEFAULT_SIDEBAR_WIDTH;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
deferred, div, px, relative, rems, App, AppContext, Axis, ClipboardItem, Context, Entity,
|
||||
@@ -14,7 +14,6 @@ use key_store::{Credential, KeyItem, KeyStore};
|
||||
use nostr_connect::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use relay_auth::RelayAuth;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::NostrRegistry;
|
||||
use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry};
|
||||
@@ -257,9 +256,9 @@ impl ChatSpace {
|
||||
|
||||
this.update_in(cx, |_, window, cx| {
|
||||
match result {
|
||||
Ok(profile) => {
|
||||
Ok(person) => {
|
||||
persons.update(cx, |this, cx| {
|
||||
this.insert(profile, cx);
|
||||
this.insert(person, cx);
|
||||
// Close the edit profile modal
|
||||
window.close_all_modals(cx);
|
||||
});
|
||||
@@ -476,7 +475,6 @@ impl ChatSpace {
|
||||
}
|
||||
|
||||
fn titlebar_right(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let auto_update = AutoUpdater::global(cx);
|
||||
|
||||
let relay_auth = RelayAuth::global(cx);
|
||||
@@ -562,9 +560,9 @@ impl ChatSpace {
|
||||
.reverse()
|
||||
.transparent()
|
||||
.icon(IconName::CaretDown)
|
||||
.child(Avatar::new(profile.avatar(proxy)).size(rems(1.45)))
|
||||
.child(Avatar::new(profile.avatar()).size(rems(1.45)))
|
||||
.popup_menu(move |this, _window, _cx| {
|
||||
this.label(profile.display_name())
|
||||
this.label(profile.name())
|
||||
.menu_with_icon(
|
||||
"Profile",
|
||||
IconName::EmojiFill,
|
||||
|
||||
@@ -13,7 +13,6 @@ use gpui::{
|
||||
use gpui_tokio::Tokio;
|
||||
use list_item::RoomListItem;
|
||||
use nostr_sdk::prelude::*;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::{NostrRegistry, GIFTWRAP_SUBSCRIPTION};
|
||||
use theme::ActiveTheme;
|
||||
@@ -540,7 +539,6 @@ impl Sidebar {
|
||||
range: Range<usize>,
|
||||
cx: &Context<Self>,
|
||||
) -> Vec<impl IntoElement> {
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let mut items = Vec::with_capacity(range.end - range.start);
|
||||
|
||||
for ix in range {
|
||||
@@ -563,7 +561,7 @@ impl Sidebar {
|
||||
RoomListItem::new(ix)
|
||||
.room_id(room_id)
|
||||
.name(this.display_name(cx))
|
||||
.avatar(this.display_image(proxy, cx))
|
||||
.avatar(this.display_image(cx))
|
||||
.public_key(member.public_key())
|
||||
.kind(this.kind)
|
||||
.created_at(this.created_at.to_ago())
|
||||
|
||||
@@ -10,6 +10,7 @@ use gpui::{
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::Person;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smol::fs;
|
||||
@@ -233,7 +234,7 @@ impl UserProfile {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn set_metadata(&mut self, cx: &mut Context<Self>) -> Task<Result<Profile, Error>> {
|
||||
pub fn set_metadata(&mut self, cx: &mut Context<Self>) -> Task<Result<Person, Error>> {
|
||||
let avatar = self.avatar_input.read(cx).value().to_string();
|
||||
let name = self.name_input.read(cx).value().to_string();
|
||||
let bio = self.bio_input.read(cx).value().to_string();
|
||||
@@ -274,7 +275,7 @@ impl UserProfile {
|
||||
|
||||
// Return the updated profile
|
||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||
let profile = Profile::new(event.pubkey, metadata);
|
||||
let profile = Person::new(event.pubkey, metadata);
|
||||
|
||||
Ok(profile)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use common::{nip05_verify, shorten_pubkey, RenderedProfile};
|
||||
use common::{nip05_verify, shorten_pubkey};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, relative, rems, App, AppContext, ClipboardItem, Context, Entity, IntoElement,
|
||||
@@ -8,8 +8,7 @@ use gpui::{
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
use person::{Person, PersonRegistry};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::NostrRegistry;
|
||||
use theme::ActiveTheme;
|
||||
@@ -23,7 +22,7 @@ pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProfileViewer {
|
||||
profile: Profile,
|
||||
profile: Person,
|
||||
|
||||
/// Follow status
|
||||
followed: bool,
|
||||
@@ -134,7 +133,6 @@ impl ProfileViewer {
|
||||
|
||||
impl Render for ProfileViewer {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let bech32 = shorten_pubkey(self.profile.public_key(), 16);
|
||||
let shared_bech32 = SharedString::from(bech32);
|
||||
|
||||
@@ -147,14 +145,14 @@ impl Render for ProfileViewer {
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_center()
|
||||
.child(Avatar::new(self.profile.avatar(proxy)).size(rems(4.)))
|
||||
.child(Avatar::new(self.profile.avatar()).size(rems(4.)))
|
||||
.child(
|
||||
v_flex()
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.line_height(relative(1.25))
|
||||
.child(self.profile.display_name()),
|
||||
.child(self.profile.name()),
|
||||
)
|
||||
.when_some(self.address(cx), |this, address| {
|
||||
this.child(
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use chat::{ChatRegistry, Room};
|
||||
use common::{nip05_profile, RenderedProfile, TextUtils, BOOTSTRAP_RELAYS};
|
||||
use common::{nip05_profile, TextUtils, BOOTSTRAP_RELAYS};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, px, relative, rems, uniform_list, App, AppContext, Context, Entity, InteractiveElement,
|
||||
@@ -13,7 +13,6 @@ use gpui::{
|
||||
use gpui_tokio::Tokio;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::NostrRegistry;
|
||||
use theme::ActiveTheme;
|
||||
@@ -359,7 +358,6 @@ impl Compose {
|
||||
}
|
||||
|
||||
fn list_items(&self, range: Range<usize>, cx: &Context<Self>) -> Vec<impl IntoElement> {
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let mut items = Vec::with_capacity(self.contacts.read(cx).len());
|
||||
|
||||
@@ -383,8 +381,8 @@ impl Compose {
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.text_sm()
|
||||
.child(Avatar::new(profile.avatar(proxy)).size(rems(1.75)))
|
||||
.child(profile.display_name()),
|
||||
.child(Avatar::new(profile.avatar()).size(rems(1.75)))
|
||||
.child(profile.name()),
|
||||
)
|
||||
.when(contact.selected, |this| {
|
||||
this.child(
|
||||
|
||||
@@ -8,8 +8,7 @@ use gpui::{
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
use person::{Person, PersonRegistry};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::NostrRegistry;
|
||||
use theme::ActiveTheme;
|
||||
@@ -23,7 +22,7 @@ pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<
|
||||
}
|
||||
|
||||
pub struct Screening {
|
||||
profile: Profile,
|
||||
profile: Person,
|
||||
verified: bool,
|
||||
followed: bool,
|
||||
last_active: Option<Timestamp>,
|
||||
@@ -225,7 +224,6 @@ impl Screening {
|
||||
|
||||
impl Render for Screening {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> 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();
|
||||
let last_active = self.last_active.map(|_| true);
|
||||
@@ -238,12 +236,12 @@ impl Render for Screening {
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_center()
|
||||
.child(Avatar::new(self.profile.avatar(proxy)).size(rems(4.)))
|
||||
.child(Avatar::new(self.profile.avatar()).size(rems(4.)))
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.line_height(relative(1.25))
|
||||
.child(self.profile.display_name()),
|
||||
.child(self.profile.name()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use common::{RenderedProfile, BUNKER_TIMEOUT};
|
||||
use common::BUNKER_TIMEOUT;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, relative, rems, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
||||
@@ -266,8 +266,8 @@ impl Render for Startup {
|
||||
)
|
||||
})
|
||||
.when(!self.loading, |this| {
|
||||
let avatar = profile.avatar(true);
|
||||
let name = profile.display_name();
|
||||
let avatar = profile.avatar();
|
||||
let name = profile.name();
|
||||
|
||||
this.child(
|
||||
h_flex()
|
||||
|
||||
@@ -7,9 +7,12 @@ use anyhow::{anyhow, Error};
|
||||
use common::{EventUtils, BOOTSTRAP_RELAYS};
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Task};
|
||||
use nostr_sdk::prelude::*;
|
||||
pub use person::*;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::{NostrRegistry, TIMEOUT};
|
||||
|
||||
mod person;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
PersonRegistry::set_global(cx.new(PersonRegistry::new), cx);
|
||||
}
|
||||
@@ -22,7 +25,7 @@ impl Global for GlobalPersonRegistry {}
|
||||
#[derive(Debug)]
|
||||
pub struct PersonRegistry {
|
||||
/// Collection of all persons (user profiles)
|
||||
persons: HashMap<PublicKey, Entity<Profile>>,
|
||||
persons: HashMap<PublicKey, Entity<Person>>,
|
||||
|
||||
/// Set of public keys that have been seen
|
||||
seen: Rc<RefCell<HashSet<PublicKey>>>,
|
||||
@@ -51,7 +54,7 @@ impl PersonRegistry {
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
// Channel for communication between nostr and gpui
|
||||
let (tx, rx) = flume::bounded::<Profile>(100);
|
||||
let (tx, rx) = flume::bounded::<Person>(100);
|
||||
let (mta_tx, mta_rx) = flume::bounded::<PublicKey>(100);
|
||||
|
||||
let mut tasks = smallvec![];
|
||||
@@ -81,9 +84,9 @@ impl PersonRegistry {
|
||||
tasks.push(
|
||||
// Update GPUI state
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Ok(profile) = rx.recv_async().await {
|
||||
while let Ok(person) = rx.recv_async().await {
|
||||
this.update(cx, |this, cx| {
|
||||
this.insert(profile, cx);
|
||||
this.insert(person, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -99,9 +102,9 @@ impl PersonRegistry {
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(profiles) => {
|
||||
Ok(persons) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.bulk_inserts(profiles, cx);
|
||||
this.bulk_inserts(persons, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -121,7 +124,7 @@ impl PersonRegistry {
|
||||
}
|
||||
|
||||
/// Handle nostr notifications
|
||||
async fn handle_notifications(client: &Client, tx: &flume::Sender<Profile>) {
|
||||
async fn handle_notifications(client: &Client, tx: &flume::Sender<Person>) {
|
||||
let mut notifications = client.notifications();
|
||||
let mut processed_events = HashSet::new();
|
||||
|
||||
@@ -140,9 +143,9 @@ impl PersonRegistry {
|
||||
match event.kind {
|
||||
Kind::Metadata => {
|
||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||
let profile = Profile::new(event.pubkey, metadata);
|
||||
let person = Person::new(event.pubkey, metadata);
|
||||
|
||||
tx.send_async(profile).await.ok();
|
||||
tx.send_async(person).await.ok();
|
||||
}
|
||||
Kind::ContactList => {
|
||||
let public_keys = event.extract_public_keys();
|
||||
@@ -214,51 +217,50 @@ impl PersonRegistry {
|
||||
}
|
||||
|
||||
/// Load all user profiles from the database
|
||||
async fn load_persons(client: &Client) -> Result<Vec<Profile>, Error> {
|
||||
async fn load_persons(client: &Client) -> Result<Vec<Person>, Error> {
|
||||
let filter = Filter::new().kind(Kind::Metadata).limit(200);
|
||||
let events = client.database().query(filter).await?;
|
||||
|
||||
let mut profiles = vec![];
|
||||
let mut persons = vec![];
|
||||
|
||||
for event in events.into_iter() {
|
||||
let metadata = Metadata::from_json(event.content).unwrap_or_default();
|
||||
let profile = Profile::new(event.pubkey, metadata);
|
||||
profiles.push(profile);
|
||||
let person = Person::new(event.pubkey, metadata);
|
||||
persons.push(person);
|
||||
}
|
||||
|
||||
Ok(profiles)
|
||||
Ok(persons)
|
||||
}
|
||||
|
||||
/// Insert batch of persons
|
||||
fn bulk_inserts(&mut self, profiles: Vec<Profile>, cx: &mut Context<Self>) {
|
||||
for profile in profiles.into_iter() {
|
||||
self.persons
|
||||
.insert(profile.public_key(), cx.new(|_| profile));
|
||||
fn bulk_inserts(&mut self, persons: Vec<Person>, cx: &mut Context<Self>) {
|
||||
for person in persons.into_iter() {
|
||||
self.persons.insert(person.public_key(), cx.new(|_| person));
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Insert or update a person
|
||||
pub fn insert(&mut self, profile: Profile, cx: &mut App) {
|
||||
let public_key = profile.public_key();
|
||||
pub fn insert(&mut self, person: Person, cx: &mut App) {
|
||||
let public_key = person.public_key();
|
||||
|
||||
match self.persons.get(&public_key) {
|
||||
Some(person) => {
|
||||
person.update(cx, |this, cx| {
|
||||
*this = profile;
|
||||
Some(this) => {
|
||||
this.update(cx, |this, cx| {
|
||||
*this = person;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
None => {
|
||||
self.persons.insert(public_key, cx.new(|_| profile));
|
||||
self.persons.insert(public_key, cx.new(|_| person));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get single person by public key
|
||||
pub fn get(&self, public_key: &PublicKey, cx: &App) -> Profile {
|
||||
if let Some(profile) = self.persons.get(public_key) {
|
||||
return profile.read(cx).clone();
|
||||
pub fn get(&self, public_key: &PublicKey, cx: &App) -> Person {
|
||||
if let Some(person) = self.persons.get(public_key) {
|
||||
return person.read(cx).clone();
|
||||
}
|
||||
|
||||
let public_key = *public_key;
|
||||
@@ -277,6 +279,6 @@ impl PersonRegistry {
|
||||
}
|
||||
|
||||
// Return a temporary profile with default metadata
|
||||
Profile::new(public_key, Metadata::default())
|
||||
Person::new(public_key, Metadata::default())
|
||||
}
|
||||
}
|
||||
|
||||
111
crates/person/src/person.rs
Normal file
111
crates/person/src/person.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use gpui::SharedString;
|
||||
use nostr_sdk::prelude::*;
|
||||
use state::Announcement;
|
||||
|
||||
/// Person
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Person {
|
||||
public_key: PublicKey,
|
||||
metadata: Metadata,
|
||||
announcement: Option<Announcement>,
|
||||
}
|
||||
|
||||
impl PartialEq for Person {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.public_key == other.public_key
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Person {}
|
||||
|
||||
impl PartialOrd for Person {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Person {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.name().cmp(&other.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Person {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.public_key.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicKey> for Person {
|
||||
fn from(public_key: PublicKey) -> Self {
|
||||
Self::new(public_key, Metadata::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Person {
|
||||
pub fn new(public_key: PublicKey, metadata: Metadata) -> Self {
|
||||
Self {
|
||||
public_key,
|
||||
metadata,
|
||||
announcement: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get profile public key
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.public_key
|
||||
}
|
||||
|
||||
/// Get profile metadata
|
||||
pub fn metadata(&self) -> Metadata {
|
||||
self.metadata.clone()
|
||||
}
|
||||
|
||||
/// Get profile encryption keys announcement
|
||||
pub fn announcement(&self) -> Option<Announcement> {
|
||||
self.announcement.clone()
|
||||
}
|
||||
|
||||
/// Get profile avatar
|
||||
pub fn avatar(&self) -> SharedString {
|
||||
self.metadata()
|
||||
.picture
|
||||
.as_ref()
|
||||
.filter(|picture| !picture.is_empty())
|
||||
.map(|picture| picture.into())
|
||||
.unwrap_or_else(|| "brand/avatar.png".into())
|
||||
}
|
||||
|
||||
/// Get profile name
|
||||
pub fn name(&self) -> SharedString {
|
||||
if let Some(display_name) = self.metadata().display_name.as_ref() {
|
||||
if !display_name.is_empty() {
|
||||
return SharedString::from(display_name);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = self.metadata().name.as_ref() {
|
||||
if !name.is_empty() {
|
||||
return SharedString::from(name);
|
||||
}
|
||||
}
|
||||
|
||||
SharedString::from(shorten_pubkey(self.public_key(), 4))
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorten a [`PublicKey`] to a string with the first and last `len` characters
|
||||
///
|
||||
/// Ex. `00000000:00000002`
|
||||
pub fn shorten_pubkey(public_key: PublicKey, len: usize) -> String {
|
||||
let Ok(pubkey) = public_key.to_bech32();
|
||||
|
||||
format!(
|
||||
"{}:{}",
|
||||
&pubkey[0..(len + 1)],
|
||||
&pubkey[pubkey.len() - len..]
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,26 @@ pub struct Announcement {
|
||||
client_name: Option<String>,
|
||||
}
|
||||
|
||||
impl From<&Event> for Announcement {
|
||||
fn from(val: &Event) -> Self {
|
||||
let public_key = val
|
||||
.tags
|
||||
.iter()
|
||||
.find(|tag| tag.kind().as_str() == "n" || tag.kind().as_str() == "P")
|
||||
.and_then(|tag| tag.content())
|
||||
.and_then(|c| PublicKey::parse(c).ok())
|
||||
.unwrap_or(val.pubkey);
|
||||
|
||||
let client_name = val
|
||||
.tags
|
||||
.find(TagKind::Client)
|
||||
.and_then(|tag| tag.content())
|
||||
.map(|c| c.to_string());
|
||||
|
||||
Self::new(val.id, client_name, public_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Announcement {
|
||||
pub fn new(id: EventId, client_name: Option<String>, public_key: PublicKey) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -528,8 +528,18 @@ impl NostrRegistry {
|
||||
.limit(1)
|
||||
.author(public_key);
|
||||
|
||||
// Filter for encryption keys announcement
|
||||
let encryption_keys = Filter::new()
|
||||
.kind(Kind::Custom(10044))
|
||||
.limit(1)
|
||||
.author(public_key);
|
||||
|
||||
client
|
||||
.subscribe_to(urls, vec![metadata, contact_list], Some(opts))
|
||||
.subscribe_to(
|
||||
urls,
|
||||
vec![metadata, contact_list, encryption_keys],
|
||||
Some(opts),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user