chore: internal changes

This commit is contained in:
2025-02-25 15:22:24 +07:00
parent 1c4806bd92
commit 111ab3b082
11 changed files with 275 additions and 323 deletions

View File

@@ -4,7 +4,6 @@ use gpui::{
Context, Entity, InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, Styled,
StyledImage, Window,
};
use nostr_sdk::prelude::*;
use serde::Deserialize;
use state::get_client;
use std::sync::Arc;
@@ -96,56 +95,27 @@ impl AppView {
}
fn verify_user_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
let Some(account) = Account::global(cx) else {
let Some(model) = Account::global(cx) else {
return;
};
let public_key = account.read(cx).get().public_key();
let client = get_client();
let account = model.read(cx);
let task = account.verify_inbox_relays(cx);
let window_handle = window.window_handle();
let (tx, rx) = oneshot::channel::<Option<Vec<String>>>();
cx.background_spawn(async move {
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let relays = client
.database()
.query(filter)
.await
.ok()
.and_then(|events| events.first_owned())
.map(|event| {
event
.tags
.filter_standardized(TagKind::Relay)
.filter_map(|t| match t {
TagStandard::Relay(url) => Some(url.to_string()),
_ => None,
})
.collect::<Vec<_>>()
});
_ = tx.send(relays);
})
.detach();
cx.spawn(|this, mut cx| async move {
if let Ok(Some(relays)) = rx.await {
if let Ok(relays) = task.await {
_ = cx.update(|cx| {
_ = this.update(cx, |this, cx| {
let relays = cx.new(|_| Some(relays));
this.relays = relays;
this.relays = cx.new(|_| Some(relays));
cx.notify();
});
});
} else {
_ = cx.update_window(window_handle, |_, window, cx| {
this.update(cx, |this: &mut Self, cx| {
_ = this.update(cx, |this: &mut Self, cx| {
this.render_setup_relays(window, cx)
})
});
});
}
})

View File

@@ -29,7 +29,8 @@ use ui::{
v_flex, ContextModal, Icon, IconName, Sizable, StyledExt,
};
const ALERT: &str =
const ALERT: &str = "has not set up Messaging (DM) Relays, so they will NOT receive your messages.";
const DESCRIPTION: &str =
"This conversation is private. Only members of this chat can see each other's messages.";
pub fn init(
@@ -186,55 +187,25 @@ impl Chat {
return;
};
let client = get_client();
let (tx, rx) = oneshot::channel::<Vec<(PublicKey, bool)>>();
let pubkeys: Vec<PublicKey> = model
.read(cx)
.members
.iter()
.map(|m| m.public_key())
.collect();
cx.background_spawn(async move {
let mut result = Vec::new();
for pubkey in pubkeys.into_iter() {
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(pubkey)
.limit(1);
let is_ready = if let Ok(events) = client.database().query(filter).await {
events.first_owned().is_some()
} else {
false
};
result.push((pubkey, is_ready));
}
_ = tx.send(result);
})
.detach();
let room = model.read(cx);
let task = room.verify_inbox_relays(cx);
cx.spawn(|this, cx| async move {
if let Ok(result) = rx.await {
if let Ok(result) = task.await {
_ = cx.update(|cx| {
_ = this.update(cx, |this, cx| {
for item in result.into_iter() {
result.into_iter().for_each(|item| {
if !item.1 {
let name = this
.room
.read_with(cx, |this, _| this.name().unwrap_or("Unnamed".into()))
.unwrap_or("Unnamed".into());
this.push_system_message(
format!("{} has not set up Messaging (DM) Relays, so they will NOT receive your messages.", name),
cx,
);
if let Ok(Some(member)) =
this.room.read_with(cx, |this, _| this.member(&item.0))
{
this.push_system_message(
format!("{} {}", ALERT, member.name()),
cx,
);
}
}
}
});
});
});
}
@@ -549,7 +520,7 @@ impl Chat {
.group_hover("", |this| this.bg(cx.theme().danger)),
)
.child(
img("brand/avatar.png")
img("brand/avatar.jpg")
.size_8()
.rounded_full()
.flex_shrink_0(),
@@ -574,7 +545,7 @@ impl Chat {
.size_8()
.text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)),
)
.child(ALERT),
.child(DESCRIPTION),
})
} else {
div()

View File

@@ -49,7 +49,7 @@ impl Profile {
TextInput::new(window, cx)
.text_size(Size::XSmall)
.small()
.placeholder("https://example.com/avatar.png")
.placeholder("https://example.com/avatar.jpg")
});
let website_input = cx.new(|cx| {
@@ -309,7 +309,7 @@ impl Render for Profile {
if picture.is_empty() {
this.child(
img("brand/avatar.png")
img("brand/avatar.jpg")
.size_10()
.rounded_full()
.flex_shrink_0(),

View File

@@ -1,6 +1,6 @@
use gpui::{
div, prelude::FluentBuilder, px, uniform_list, AppContext, Context, Entity, FocusHandle,
InteractiveElement, IntoElement, ParentElement, Render, Styled, TextAlign, Window,
InteractiveElement, IntoElement, ParentElement, Render, Styled, Task, TextAlign, Window,
};
use nostr_sdk::prelude::*;
use state::get_client;
@@ -63,31 +63,23 @@ impl Relays {
let relays = self.relays.read(cx).clone();
let window_handle = window.window_handle();
// Show loading spinner
self.set_loading(true, cx);
let client = get_client();
let (tx, rx) = oneshot::channel();
cx.background_spawn(async move {
let signer = client.signer().await.expect("Signer is required");
let public_key = signer
.get_public_key()
.await
.expect("Cannot get public key");
let task: Task<Result<EventId, anyhow::Error>> = cx.background_spawn(async move {
let client = get_client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
// If user didn't have any NIP-65 relays, add default ones
// TODO: Is this really necessary?
if let Ok(relay_list) = client.database().relay_list(public_key).await {
if relay_list.is_empty() {
let builder = EventBuilder::relay_list(vec![
(RelayUrl::parse("wss://relay.damus.io/").unwrap(), None),
(RelayUrl::parse("wss://relay.primal.net/").unwrap(), None),
(RelayUrl::parse("wss://nos.lol/").unwrap(), None),
]);
if client.database().relay_list(public_key).await?.is_empty() {
let builder = EventBuilder::relay_list(vec![
(RelayUrl::parse("wss://relay.damus.io/").unwrap(), None),
(RelayUrl::parse("wss://relay.primal.net/").unwrap(), None),
]);
if let Err(e) = client.send_event_builder(builder).await {
log::error!("Failed to send relay list event: {}", e)
}
if let Err(e) = client.send_event_builder(builder).await {
log::error!("Failed to send relay list event: {}", e);
}
}
@@ -97,15 +89,13 @@ impl Relays {
.collect();
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(tags);
let output = client.send_event_builder(builder).await?;
if let Ok(output) = client.send_event_builder(builder).await {
_ = tx.send(output.val);
};
})
.detach();
Ok(output.val)
});
cx.spawn(|this, mut cx| async move {
if rx.await.is_ok() {
if task.await.is_ok() {
_ = cx.update_window(window_handle, |_, window, cx| {
_ = this.update(cx, |this, cx| {
this.set_loading(false, cx);

View File

@@ -3,10 +3,12 @@ use common::{profile::NostrProfile, utils::random_name};
use gpui::{
div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, TextAlign, Window,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextAlign,
Window,
};
use nostr_sdk::prelude::*;
use serde::Deserialize;
use smallvec::{smallvec, SmallVec};
use smol::Timer;
use state::get_client;
use std::{collections::HashSet, time::Duration};
@@ -17,7 +19,7 @@ use ui::{
ContextModal, Icon, IconName, Sizable, Size, StyledExt,
};
const ALERT: &str =
const DESCRIPTION: &str =
"Start a conversation with someone using their npub or NIP-05 (like foo@bar.com).";
#[derive(Clone, PartialEq, Eq, Deserialize)]
@@ -35,7 +37,7 @@ pub struct Compose {
is_submitting: bool,
error_message: Entity<Option<SharedString>>,
#[allow(dead_code)]
subscriptions: Vec<Subscription>,
subscriptions: SmallVec<[Subscription; 1]>,
}
impl Compose {
@@ -43,7 +45,6 @@ impl Compose {
let contacts = cx.new(|_| Vec::new());
let selected = cx.new(|_| HashSet::new());
let error_message = cx.new(|_| None);
let mut subscriptions = Vec::new();
let title_input = cx.new(|cx| {
let name = random_name(2);
@@ -63,17 +64,15 @@ impl Compose {
.placeholder("npub1...")
});
let mut subscriptions = smallvec![];
// Handle Enter event for user input
subscriptions.push(cx.subscribe_in(
&user_input,
window,
move |this, input, input_event, window, cx| {
move |this, _, input_event, window, cx| {
if let InputEvent::PressEnter = input_event {
if input.read(cx).text().contains("@") {
this.add_nip05(window, cx);
} else {
this.add_npub(window, cx)
}
this.add(window, cx);
}
},
));
@@ -147,50 +146,48 @@ impl Compose {
}
let tags = Tags::from_list(tag_list);
let client = get_client();
let window_handle = window.window_handle();
let (tx, rx) = oneshot::channel::<Event>();
cx.background_spawn(async move {
let signer = client.signer().await.expect("Signer is required");
let event: Task<Result<Event, anyhow::Error>> = cx.background_spawn(async move {
let client = get_client();
let signer = client.signer().await?;
// [IMPORTANT]
// Make sure this event is never send,
// this event existed just use for convert to Coop's Chat Room later.
if let Ok(event) = EventBuilder::private_msg_rumor(*pubkeys.last().unwrap(), "")
let event = EventBuilder::private_msg_rumor(*pubkeys.last().unwrap(), "")
.tags(tags)
.sign(&signer)
.await
{
_ = tx.send(event)
};
})
.detach();
.await?;
Ok(event)
});
cx.spawn(|this, mut cx| async move {
if let Ok(event) = rx.await {
if let Ok(event) = event.await {
_ = cx.update_window(window_handle, |_, window, cx| {
// Stop loading spinner
_ = this.update(cx, |this, cx| {
this.set_submitting(false, cx);
});
if let Some(chats) = ChatRegistry::global(cx) {
let room = Room::new(&event, cx);
let Some(chats) = ChatRegistry::global(cx) else {
return;
};
let room = Room::new(&event, cx);
chats.update(cx, |state, cx| {
match state.push_room(room, cx) {
Ok(_) => {
// TODO: open chat panel
window.close_modal(cx);
}
Err(e) => {
_ = this.update(cx, |this, cx| {
this.set_error(Some(e.to_string().into()), cx);
});
}
chats.update(cx, |state, cx| {
match state.push_room(room, cx) {
Ok(_) => {
// TODO: automatically open newly created chat panel
window.close_modal(cx);
}
});
}
Err(e) => {
_ = this.update(cx, |this, cx| {
this.set_error(Some(e.to_string().into()), cx);
});
}
}
});
});
}
})
@@ -209,128 +206,77 @@ impl Compose {
self.is_submitting
}
fn add_npub(&mut self, window: &mut Window, cx: &mut Context<Self>) {
fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let client = get_client();
let window_handle = window.window_handle();
let content = self.user_input.read(cx).text().to_string();
// Show loading spinner
self.set_loading(true, cx);
let Ok(public_key) = PublicKey::parse(&content) else {
self.set_loading(false, cx);
self.set_error(Some("Public Key is not valid".into()), cx);
return;
let task: Task<Result<NostrProfile, anyhow::Error>> = if content.starts_with("npub1") {
let Ok(public_key) = PublicKey::parse(&content) else {
self.set_loading(false, cx);
self.set_error(Some("Public Key is not valid".into()), cx);
return;
};
cx.background_spawn(async move {
let metadata = client
.fetch_metadata(public_key, Duration::from_secs(2))
.await?;
Ok(NostrProfile::new(public_key, metadata))
})
} else {
cx.background_spawn(async move {
let profile = nip05::profile(&content, None).await?;
let public_key = profile.public_key;
let metadata = client
.fetch_metadata(public_key, Duration::from_secs(2))
.await?;
Ok(NostrProfile::new(public_key, metadata))
})
};
if self
.contacts
.read(cx)
.iter()
.any(|c| c.public_key() == public_key)
{
self.set_loading(false, cx);
return;
};
let client = get_client();
let (tx, rx) = oneshot::channel::<Metadata>();
cx.background_spawn(async move {
let metadata = (client
.fetch_metadata(public_key, Duration::from_secs(2))
.await)
.unwrap_or_default();
_ = tx.send(metadata);
})
.detach();
cx.spawn(|this, mut cx| async move {
if let Ok(metadata) = rx.await {
_ = cx.update_window(window_handle, |_, window, cx| {
_ = this.update(cx, |this, cx| {
this.contacts.update(cx, |this, cx| {
this.insert(0, NostrProfile::new(public_key, metadata));
cx.notify();
});
match task.await {
Ok(profile) => {
_ = cx.update_window(window_handle, |_, window, cx| {
_ = this.update(cx, |this, cx| {
let public_key = profile.public_key();
this.selected.update(cx, |this, cx| {
this.insert(public_key);
cx.notify();
});
this.contacts.update(cx, |this, cx| {
this.insert(0, profile);
cx.notify();
});
// Stop loading indicator
this.set_loading(false, cx);
this.selected.update(cx, |this, cx| {
this.insert(public_key);
cx.notify();
});
// Clear input
this.user_input.update(cx, |this, cx| {
this.set_text("", window, cx);
cx.notify();
// Stop loading indicator
this.set_loading(false, cx);
// Clear input
this.user_input.update(cx, |this, cx| {
this.set_text("", window, cx);
cx.notify();
});
});
});
});
}
})
.detach();
}
fn add_nip05(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let window_handle = window.window_handle();
let content = self.user_input.read(cx).text().to_string();
// Show loading spinner
self.set_loading(true, cx);
let client = get_client();
let (tx, rx) = oneshot::channel::<Option<NostrProfile>>();
cx.background_spawn(async move {
if let Ok(profile) = nip05::profile(&content, None).await {
let metadata = (client
.fetch_metadata(profile.public_key, Duration::from_secs(2))
.await)
.unwrap_or_default();
_ = tx.send(Some(NostrProfile::new(profile.public_key, metadata)));
} else {
_ = tx.send(None);
}
})
.detach();
cx.spawn(|this, mut cx| async move {
if let Ok(Some(profile)) = rx.await {
_ = cx.update_window(window_handle, |_, window, cx| {
_ = this.update(cx, |this, cx| {
let public_key = profile.public_key();
this.contacts.update(cx, |this, cx| {
this.insert(0, profile);
cx.notify();
});
this.selected.update(cx, |this, cx| {
this.insert(public_key);
cx.notify();
});
// Stop loading indicator
this.set_loading(false, cx);
// Clear input
this.user_input.update(cx, |this, cx| {
this.set_text("", window, cx);
cx.notify();
}
Err(e) => {
_ = cx.update_window(window_handle, |_, _, cx| {
_ = this.update(cx, |this, cx| {
this.set_loading(false, cx);
this.set_error(Some(e.to_string().into()), cx);
});
});
});
} else {
_ = cx.update_window(window_handle, |_, _, cx| {
_ = this.update(cx, |this, cx| {
this.set_loading(false, cx);
this.set_error(Some("NIP-05 Address is not valid".into()), cx);
});
});
}
}
})
.detach();
@@ -395,7 +341,7 @@ impl Render for Compose {
.px_2()
.text_xs()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.child(ALERT),
.child(DESCRIPTION),
)
.when_some(self.error_message.read(cx).as_ref(), |this, msg| {
this.child(
@@ -439,11 +385,7 @@ impl Render for Compose {
.rounded(ButtonRounded::Size(px(9999.)))
.loading(self.is_loading)
.on_click(cx.listener(|this, _, window, cx| {
if this.user_input.read(cx).text().contains("@") {
this.add_nip05(window, cx);
} else {
this.add_npub(window, cx);
}
this.add(window, cx);
})),
)
.child(self.user_input.clone()),