refactor chats (#15)
* refactor * update * update * update * remove nostrprofile struct * update * refactor contacts * prevent double login
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use account::Account;
|
||||
use common::profile::SharedProfile;
|
||||
use global::get_client;
|
||||
use gpui::{
|
||||
actions, div, img, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis,
|
||||
@@ -172,7 +173,7 @@ impl ChatSpace {
|
||||
.icon(Icon::new(IconName::ChevronDownSmall))
|
||||
.when_some(
|
||||
Account::global(cx).read(cx).profile.as_ref(),
|
||||
|this, profile| this.child(img(profile.avatar.clone()).size_5()),
|
||||
|this, profile| this.child(img(profile.shared_avatar()).size_5()),
|
||||
)
|
||||
.popup_menu(move |this, _, _cx| {
|
||||
this.menu(
|
||||
|
||||
@@ -18,7 +18,8 @@ use gpui::{point, SharedString, TitlebarOptions};
|
||||
use gpui::{WindowBackgroundAppearance, WindowDecorations};
|
||||
use nostr_sdk::{
|
||||
pool::prelude::ReqExitPolicy, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind,
|
||||
PublicKey, RelayMessage, RelayPoolNotification, SubscribeAutoCloseOptions, SubscriptionId, Tag,
|
||||
Metadata, PublicKey, RelayMessage, RelayPoolNotification, SubscribeAutoCloseOptions,
|
||||
SubscriptionId, Tag,
|
||||
};
|
||||
use smol::Timer;
|
||||
use std::{collections::HashSet, mem, sync::Arc, time::Duration};
|
||||
@@ -34,6 +35,8 @@ actions!(coop, [Quit]);
|
||||
enum Signal {
|
||||
/// Receive event
|
||||
Event(Event),
|
||||
/// Receive metadata
|
||||
Metadata(Box<(PublicKey, Option<Metadata>)>),
|
||||
/// Receive EOSE
|
||||
Eose,
|
||||
}
|
||||
@@ -149,6 +152,14 @@ fn main() {
|
||||
event_tx.send(Signal::Event(event)).await.ok();
|
||||
}
|
||||
}
|
||||
Kind::Metadata => {
|
||||
let metadata = Metadata::from_json(&event.content).ok();
|
||||
|
||||
event_tx
|
||||
.send(Signal::Metadata(Box::new((event.pubkey, metadata))))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
Kind::ContactList => {
|
||||
if let Ok(signer) = client.signer().await {
|
||||
if let Ok(public_key) = signer.get_public_key().await {
|
||||
@@ -241,14 +252,23 @@ fn main() {
|
||||
while let Ok(signal) = event_rx.recv().await {
|
||||
cx.update(|window, cx| {
|
||||
match signal {
|
||||
Signal::Eose => {
|
||||
chats.update(cx, |this, cx| this.load_rooms(window, cx));
|
||||
}
|
||||
Signal::Event(event) => {
|
||||
chats.update(cx, |this, cx| {
|
||||
this.push_message(event, window, cx)
|
||||
});
|
||||
}
|
||||
Signal::Metadata(data) => {
|
||||
chats.update(cx, |this, cx| {
|
||||
this.add_profile(data.0, data.1, cx)
|
||||
});
|
||||
}
|
||||
Signal::Eose => {
|
||||
chats.update(cx, |this, cx| {
|
||||
// This function maybe called multiple times
|
||||
// TODO: only handle the last EOSE signal
|
||||
this.load_rooms(window, cx)
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Error};
|
||||
use async_utility::task::spawn;
|
||||
use chats::{message::RoomMessage, room::Room, ChatRegistry};
|
||||
use common::utils::nip96_upload;
|
||||
use common::{nip96_upload, profile::SharedProfile};
|
||||
use global::{constants::IMAGE_SERVICE, get_client};
|
||||
use gpui::{
|
||||
div, img, list, prelude::FluentBuilder, px, relative, svg, white, AnyElement, App, AppContext,
|
||||
@@ -27,7 +27,7 @@ use ui::{
|
||||
const ALERT: &str = "has not set up Messaging (DM) Relays, so they will NOT receive your messages.";
|
||||
|
||||
pub fn init(id: &u64, window: &mut Window, cx: &mut App) -> Result<Arc<Entity<Chat>>, Error> {
|
||||
if let Some(room) = ChatRegistry::global(cx).read(cx).get(id, cx) {
|
||||
if let Some(room) = ChatRegistry::global(cx).read(cx).room(id, cx) {
|
||||
Ok(Arc::new(Chat::new(id, room, window, cx)))
|
||||
} else {
|
||||
Err(anyhow!("Chat Room not found."))
|
||||
@@ -137,14 +137,14 @@ impl Chat {
|
||||
this.update(cx, |this, cx| {
|
||||
result.into_iter().for_each(|item| {
|
||||
if !item.1 {
|
||||
if let Some(profile) =
|
||||
this.room.read_with(cx, |this, _| this.member(&item.0))
|
||||
{
|
||||
this.push_system_message(
|
||||
format!("{} {}", profile.name, ALERT),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
let profile = this
|
||||
.room
|
||||
.read_with(cx, |this, _| this.profile_by_pubkey(&item.0, cx));
|
||||
|
||||
this.push_system_message(
|
||||
format!("{} {}", profile.shared_name(), ALERT),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
@@ -367,7 +367,7 @@ impl Chat {
|
||||
this.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
||||
}),
|
||||
)
|
||||
.child(img(item.author.avatar.clone()).size_8().flex_shrink_0())
|
||||
.child(img(item.author.shared_avatar()).size_8().flex_shrink_0())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
@@ -381,17 +381,11 @@ impl Chat {
|
||||
.gap_2()
|
||||
.text_xs()
|
||||
.child(
|
||||
div().font_semibold().child(item.author.name.clone()),
|
||||
div().font_semibold().child(item.author.shared_name()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(item.created_at.human_readable())
|
||||
.text_color(
|
||||
cx.theme()
|
||||
.base
|
||||
.step(cx, ColorScaleStep::ELEVEN),
|
||||
),
|
||||
),
|
||||
.child(div().child(item.ago()).text_color(
|
||||
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||
)),
|
||||
)
|
||||
.child(div().text_sm().child(text.element(
|
||||
"body".into(),
|
||||
@@ -445,11 +439,7 @@ impl Panel for Chat {
|
||||
|
||||
fn title(&self, cx: &App) -> AnyElement {
|
||||
self.room.read_with(cx, |this, _| {
|
||||
let facepill: Vec<SharedString> = this
|
||||
.members
|
||||
.iter()
|
||||
.map(|member| member.avatar.clone())
|
||||
.collect();
|
||||
let facepill: Vec<SharedString> = this.avatars(cx);
|
||||
|
||||
div()
|
||||
.flex()
|
||||
@@ -461,13 +451,19 @@ impl Panel for Chat {
|
||||
.flex_row_reverse()
|
||||
.items_center()
|
||||
.justify_start()
|
||||
.children(facepill.into_iter().enumerate().rev().map(|(ix, face)| {
|
||||
div()
|
||||
.when(ix > 0, |div| div.ml_neg_1())
|
||||
.child(img(face).size_4())
|
||||
})),
|
||||
.children(
|
||||
facepill
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.map(|(ix, facepill)| {
|
||||
div()
|
||||
.when(ix > 0, |div| div.ml_neg_1())
|
||||
.child(img(facepill).size_4())
|
||||
}),
|
||||
),
|
||||
)
|
||||
.when_some(this.subject(), |this, name| this.child(name))
|
||||
.child(this.display_name(cx))
|
||||
.into_any()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use common::profile::NostrProfile;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use anyhow::Error;
|
||||
use common::profile::SharedProfile;
|
||||
use global::get_client;
|
||||
use gpui::{
|
||||
div, img, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context,
|
||||
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, SharedString, Styled, Window,
|
||||
Render, SharedString, Styled, Task, Window,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use ui::{
|
||||
button::Button,
|
||||
@@ -20,7 +24,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Contacts> {
|
||||
}
|
||||
|
||||
pub struct Contacts {
|
||||
contacts: Entity<Option<Vec<NostrProfile>>>,
|
||||
contacts: Option<Vec<Profile>>,
|
||||
// Panel
|
||||
name: SharedString,
|
||||
closable: bool,
|
||||
@@ -29,48 +33,42 @@ pub struct Contacts {
|
||||
}
|
||||
|
||||
impl Contacts {
|
||||
pub fn new(_window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
let contacts = cx.new(|_| None);
|
||||
let async_contact = contacts.clone();
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| Self::view(window, cx))
|
||||
}
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let client = get_client();
|
||||
let (tx, rx) = oneshot::channel::<Vec<NostrProfile>>();
|
||||
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
let signer = client.signer().await.unwrap();
|
||||
let public_key = signer.get_public_key().await.unwrap();
|
||||
let task: Task<Result<BTreeSet<Profile>, Error>> = cx.background_spawn(async move {
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let profiles = client.database().contacts(public_key).await?;
|
||||
|
||||
if let Ok(profiles) = client.database().contacts(public_key).await {
|
||||
let members: Vec<NostrProfile> = profiles
|
||||
.into_iter()
|
||||
.map(|profile| {
|
||||
NostrProfile::new(profile.public_key(), profile.metadata())
|
||||
})
|
||||
.collect();
|
||||
Ok(profiles)
|
||||
});
|
||||
|
||||
_ = tx.send(members);
|
||||
}
|
||||
if let Ok(contacts) = task.await {
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.contacts = Some(contacts.into_iter().collect_vec());
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Ok(contacts) = rx.await {
|
||||
_ = cx.update_entity(&async_contact, |this, cx| {
|
||||
*this = Some(contacts);
|
||||
cx.notify();
|
||||
});
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.new(|cx| Self {
|
||||
contacts,
|
||||
Self {
|
||||
contacts: None,
|
||||
name: "Contacts".into(),
|
||||
closable: true,
|
||||
zoomable: true,
|
||||
focus_handle: cx.focus_handle(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +109,7 @@ impl Focusable for Contacts {
|
||||
impl Render for Contacts {
|
||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().size_full().pt_2().px_2().map(|this| {
|
||||
if let Some(contacts) = self.contacts.read(cx).clone() {
|
||||
if let Some(contacts) = self.contacts.clone() {
|
||||
this.child(
|
||||
uniform_list(
|
||||
cx.entity().clone(),
|
||||
@@ -141,9 +139,9 @@ impl Render for Contacts {
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.child(img(item.avatar).size_6()),
|
||||
.child(img(item.shared_avatar()).size_6()),
|
||||
)
|
||||
.child(item.name),
|
||||
.child(item.shared_name()),
|
||||
)
|
||||
.hover(|this| {
|
||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use account::Account;
|
||||
use common::utils::create_qr;
|
||||
use common::create_qr;
|
||||
use global::get_client_keys;
|
||||
use gpui::{
|
||||
div, img, prelude::FluentBuilder, relative, AnyElement, App, AppContext, Context, Entity,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use account::Account;
|
||||
use async_utility::task::spawn;
|
||||
use common::utils::nip96_upload;
|
||||
use common::nip96_upload;
|
||||
use global::{constants::IMAGE_SERVICE, get_client};
|
||||
use gpui::{
|
||||
div, img, prelude::FluentBuilder, px, relative, AnyElement, App, AppContext, Context, Entity,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use async_utility::task::spawn;
|
||||
use common::utils::nip96_upload;
|
||||
use common::nip96_upload;
|
||||
use global::{constants::IMAGE_SERVICE, get_client};
|
||||
use gpui::{
|
||||
div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use anyhow::Error;
|
||||
use chats::{
|
||||
room::{Room, RoomKind},
|
||||
ChatRegistry,
|
||||
};
|
||||
use common::{profile::NostrProfile, utils::random_name};
|
||||
use common::{profile::SharedProfile, random_name};
|
||||
use global::get_client;
|
||||
use gpui::{
|
||||
div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
|
||||
@@ -14,7 +15,11 @@ use nostr_sdk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smol::Timer;
|
||||
use std::{collections::HashSet, rc::Rc, time::Duration};
|
||||
use std::{
|
||||
collections::{BTreeSet, HashSet},
|
||||
rc::Rc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{
|
||||
button::{Button, ButtonRounded},
|
||||
input::{InputEvent, TextInput},
|
||||
@@ -33,7 +38,7 @@ impl_internal_actions!(contacts, [SelectContact]);
|
||||
pub struct Compose {
|
||||
title_input: Entity<TextInput>,
|
||||
user_input: Entity<TextInput>,
|
||||
contacts: Entity<Vec<NostrProfile>>,
|
||||
contacts: Entity<Vec<Profile>>,
|
||||
selected: Entity<HashSet<PublicKey>>,
|
||||
focus_handle: FocusHandle,
|
||||
is_loading: bool,
|
||||
@@ -80,26 +85,17 @@ impl Compose {
|
||||
},
|
||||
));
|
||||
|
||||
let client = get_client();
|
||||
let (tx, rx) = oneshot::channel::<Vec<NostrProfile>>();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let signer = client.signer().await.unwrap();
|
||||
let public_key = signer.get_public_key().await.unwrap();
|
||||
|
||||
if let Ok(profiles) = client.database().contacts(public_key).await {
|
||||
let members: Vec<NostrProfile> = profiles
|
||||
.into_iter()
|
||||
.map(|profile| NostrProfile::new(profile.public_key(), profile.metadata()))
|
||||
.collect();
|
||||
|
||||
_ = tx.send(members);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Ok(contacts) = rx.await {
|
||||
let task: Task<Result<BTreeSet<Profile>, Error>> = cx.background_spawn(async move {
|
||||
let client = get_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let profiles = client.database().contacts(public_key).await?;
|
||||
|
||||
Ok(profiles)
|
||||
});
|
||||
|
||||
if let Ok(contacts) = task.await {
|
||||
cx.update(|cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.contacts.update(cx, |this, cx| {
|
||||
@@ -107,6 +103,7 @@ impl Compose {
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -174,7 +171,7 @@ impl Compose {
|
||||
.ok();
|
||||
|
||||
let chats = ChatRegistry::global(cx);
|
||||
let room = Room::new(&event, RoomKind::Ongoing);
|
||||
let room = Room::new(&event).kind(RoomKind::Ongoing);
|
||||
|
||||
chats.update(cx, |chats, cx| {
|
||||
match chats.push(room, cx) {
|
||||
@@ -215,7 +212,7 @@ impl Compose {
|
||||
// Show loading spinner
|
||||
self.set_loading(true, cx);
|
||||
|
||||
let task: Task<Result<NostrProfile, anyhow::Error>> = if content.contains("@") {
|
||||
let task: Task<Result<Profile, anyhow::Error>> = if content.contains("@") {
|
||||
cx.background_spawn(async move {
|
||||
let profile = nip05::profile(&content, None).await?;
|
||||
let public_key = profile.public_key;
|
||||
@@ -225,7 +222,7 @@ impl Compose {
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(NostrProfile::new(public_key, metadata))
|
||||
Ok(Profile::new(public_key, metadata))
|
||||
})
|
||||
} else {
|
||||
let Ok(public_key) = PublicKey::parse(&content) else {
|
||||
@@ -240,7 +237,7 @@ impl Compose {
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(NostrProfile::new(public_key, metadata))
|
||||
Ok(Profile::new(public_key, metadata))
|
||||
})
|
||||
};
|
||||
|
||||
@@ -249,7 +246,7 @@ impl Compose {
|
||||
Ok(profile) => {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
let public_key = profile.public_key;
|
||||
let public_key = profile.public_key();
|
||||
|
||||
this.contacts.update(cx, |this, cx| {
|
||||
this.insert(0, profile);
|
||||
@@ -441,7 +438,7 @@ impl Render for Compose {
|
||||
|
||||
for ix in range {
|
||||
let item = contacts.get(ix).unwrap().clone();
|
||||
let is_select = selected.contains(&item.public_key);
|
||||
let is_select = selected.contains(&item.public_key());
|
||||
|
||||
items.push(
|
||||
div()
|
||||
@@ -458,12 +455,10 @@ impl Render for Compose {
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.text_xs()
|
||||
.child(
|
||||
div().flex_shrink_0().child(
|
||||
img(item.avatar).size_6(),
|
||||
),
|
||||
)
|
||||
.child(item.name),
|
||||
.child(div().flex_shrink_0().child(
|
||||
img(item.shared_avatar()).size_6(),
|
||||
))
|
||||
.child(item.shared_name()),
|
||||
)
|
||||
.when(is_select, |this| {
|
||||
this.child(
|
||||
@@ -484,7 +479,7 @@ impl Render for Compose {
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(SelectContact(
|
||||
item.public_key,
|
||||
item.public_key(),
|
||||
)),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -138,31 +138,18 @@ impl Sidebar {
|
||||
|
||||
for room in rooms {
|
||||
let room = room.read(cx);
|
||||
let room_id = room.id;
|
||||
let ago = room.last_seen().ago();
|
||||
let Some(member) = room.first_member() else {
|
||||
continue;
|
||||
};
|
||||
let id = room.id;
|
||||
let ago = room.ago();
|
||||
let label = room.display_name(cx);
|
||||
let img = room.display_image(cx).map(img);
|
||||
|
||||
let label = if room.is_group() {
|
||||
room.subject().unwrap_or("Unnamed".into())
|
||||
} else {
|
||||
member.name.clone()
|
||||
};
|
||||
|
||||
let img = if !room.is_group() {
|
||||
Some(img(member.avatar.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let item = FolderItem::new(room_id as usize)
|
||||
let item = FolderItem::new(id as usize)
|
||||
.label(label)
|
||||
.description(ago)
|
||||
.img(img)
|
||||
.on_click({
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.open_room(room_id, window, cx);
|
||||
this.open_room(id, window, cx);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user