From fe24f84b022ce0c9f3d8dea4452615ced12c3beb Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 8 Jan 2025 08:02:24 +0700 Subject: [PATCH] wip: refactor --- crates/app/src/asset.rs | 2 +- crates/app/src/constants.rs | 2 +- crates/app/src/main.rs | 94 +++---- crates/app/src/states/chat.rs | 153 +++++++---- crates/app/src/states/metadata.rs | 43 ---- crates/app/src/states/mod.rs | 2 - crates/app/src/states/signal.rs | 30 --- crates/app/src/utils.rs | 12 +- crates/app/src/views/account.rs | 38 +-- crates/app/src/views/app.rs | 26 +- crates/app/src/views/chat/message.rs | 6 +- crates/app/src/views/chat/mod.rs | 10 +- crates/app/src/views/chat/room.rs | 95 ++++--- crates/app/src/views/contact/item.rs | 113 -------- crates/app/src/views/contact/mod.rs | 146 ----------- crates/app/src/views/inbox/item.rs | 153 ----------- crates/app/src/views/inbox/mod.rs | 216 ---------------- crates/app/src/views/mod.rs | 6 +- crates/app/src/views/onboarding/mod.rs | 5 +- crates/app/src/views/sidebar/contact_list.rs | 10 +- crates/app/src/views/sidebar/inbox.rs | 255 +++++++++++++++++++ crates/app/src/views/sidebar/mod.rs | 37 +-- crates/app/src/views/welcome.rs | 5 +- crates/ui/src/tab/tab.rs | 7 +- 24 files changed, 521 insertions(+), 945 deletions(-) delete mode 100644 crates/app/src/states/metadata.rs delete mode 100644 crates/app/src/states/signal.rs delete mode 100644 crates/app/src/views/contact/item.rs delete mode 100644 crates/app/src/views/contact/mod.rs delete mode 100644 crates/app/src/views/inbox/item.rs delete mode 100644 crates/app/src/views/inbox/mod.rs create mode 100644 crates/app/src/views/sidebar/inbox.rs diff --git a/crates/app/src/asset.rs b/crates/app/src/asset.rs index b31d392..e949d32 100644 --- a/crates/app/src/asset.rs +++ b/crates/app/src/asset.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use gpui::*; +use gpui::{AssetSource, Result, SharedString}; use rust_embed::RustEmbed; #[derive(RustEmbed)] diff --git a/crates/app/src/constants.rs b/crates/app/src/constants.rs index 02e003b..1d1ca35 100644 --- a/crates/app/src/constants.rs +++ b/crates/app/src/constants.rs @@ -3,5 +3,5 @@ pub const APP_NAME: &str = "Coop"; pub const FAKE_SIG: &str = "f9e79d141c004977192d05a86f81ec7c585179c371f7350a5412d33575a2a356433f58e405c2296ed273e2fe0aafa25b641e39cc4e1f3f261ebf55bce0cbac83"; pub const NEW_MESSAGE_SUB_ID: &str = "listen_new_giftwrap"; pub const ALL_MESSAGES_SUB_ID: &str = "listen_all_giftwraps"; -pub const METADATA_DELAY: u64 = 150; +pub const METADATA_DELAY: u64 = 100; pub const IMAGE_SERVICE: &str = "https://wsrv.nl"; diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index 6a1c562..3a786ea 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -1,7 +1,14 @@ use asset::Assets; +use constants::{ + ALL_MESSAGES_SUB_ID, APP_NAME, FAKE_SIG, KEYRING_SERVICE, METADATA_DELAY, NEW_MESSAGE_SUB_ID, +}; use dirs::config_dir; -use gpui::*; +use gpui::{ + actions, point, px, size, App, AppContext, Bounds, SharedString, TitlebarOptions, + VisualContext, WindowBounds, WindowDecorations, WindowKind, WindowOptions, +}; use nostr_sdk::prelude::*; +use states::{account::AccountRegistry, chat::ChatRegistry}; use std::{ collections::HashSet, fs, @@ -13,20 +20,9 @@ use tokio::{ sync::{mpsc, Mutex}, time::sleep, }; - -use constants::{ - ALL_MESSAGES_SUB_ID, APP_NAME, FAKE_SIG, KEYRING_SERVICE, METADATA_DELAY, NEW_MESSAGE_SUB_ID, -}; use ui::Root; use views::app::AppView; -use states::{ - account::AccountRegistry, - chat::ChatRegistry, - metadata::MetadataRegistry, - signal::{Signal, SignalRegistry}, -}; - mod asset; mod constants; mod states; @@ -38,6 +34,14 @@ actions!(app, [ReloadMetadata]); static CLIENT: OnceLock = OnceLock::new(); +#[derive(Clone)] +pub enum Signal { + /// Receive event + Event(Event), + /// Receive EOSE + Eose, +} + pub fn initialize_client() { // Setup app data folder let config_dir = config_dir().expect("Config directory not found"); @@ -84,14 +88,11 @@ async fn main() { _ = client.connect().await; // Signal - let (signal_tx, mut signal_rx) = mpsc::channel::(4096); // TODO: adjust? - let (mta_tx, mut mta_rx) = mpsc::unbounded_channel::(); - - // Re use sender - let mta_tx_clone = mta_tx.clone(); + let (signal_tx, mut signal_rx) = mpsc::channel::(4096); + let (mta_tx, mut mta_rx) = mpsc::channel::(4096); // Handle notification from Relays - // Send notfiy back to GPUI + // Send notify back to GPUI tokio::spawn(async move { let sig = Signature::from_str(FAKE_SIG).unwrap(); let new_message = SubscriptionId::new(NEW_MESSAGE_SUB_ID); @@ -107,20 +108,23 @@ async fn main() { { if event.kind == Kind::GiftWrap { match client.unwrap_gift_wrap(&event).await { - Ok(UnwrappedGift { rumor, .. }) => { - let mut rumor_clone = rumor.clone(); + Ok(UnwrappedGift { mut rumor, sender }) => { + // Request metadata + if let Err(e) = mta_tx.send(sender).await { + println!("Send error: {}", e) + }; // Compute event id if not exist - rumor_clone.ensure_id(); + rumor.ensure_id(); - if let Some(id) = rumor_clone.id { + if let Some(id) = rumor.id { let ev = Event::new( id, - rumor_clone.pubkey, - rumor_clone.created_at, - rumor_clone.kind, - rumor_clone.tags, - rumor_clone.content, + rumor.pubkey, + rumor.created_at, + rumor.kind, + rumor.tags, + rumor.content, sig, ); @@ -139,10 +143,6 @@ async fn main() { } Err(e) => println!("Unwrap error: {}", e), } - } else if event.kind == Kind::Metadata { - if let Err(e) = signal_tx.send(Signal::Metadata(event.pubkey)).await { - println!("Send error: {}", e) - } } } else if let RelayMessage::EndOfStoredEvents(subscription_id) = message { if subscription_id == all_messages { @@ -183,10 +183,7 @@ async fn main() { .kind(Kind::Metadata) .limit(total); - let opts = SubscribeAutoCloseOptions::default() - .exit_policy(ReqExitPolicy::WaitDurationAfterEOSE(Duration::from_secs(2))); - - if let Err(e) = client.subscribe(vec![filter], Some(opts)).await { + if let Err(e) = client.sync(filter, &SyncOptions::default()).await { println!("Error: {}", e); } } @@ -200,12 +197,8 @@ async fn main() { .run(move |cx| { // Account state AccountRegistry::set_global(cx); - // Metadata state - MetadataRegistry::set_global(cx); // Chat state ChatRegistry::set_global(cx); - // Signal state - SignalRegistry::set_global(cx, mta_tx_clone); // Initialize components ui::init(cx); @@ -224,6 +217,7 @@ async fn main() { let keys = Keys::parse(&hex).unwrap(); _ = client.set_signer(keys).await; + // Update global state _ = async_cx.update_global::(|state, _cx| { state.set_user(Some(public_key)); @@ -260,8 +254,8 @@ async fn main() { while let Ok(signal) = rx.recv().await { match signal { Signal::Eose => { - _ = async_cx.update_global::(|state, _| { - state.update(); + _ = async_cx.update_global::(|state, cx| { + state.init(cx); }); } Signal::Event(event) => { @@ -277,23 +271,7 @@ async fn main() { .await; _ = async_cx.update_global::(|state, _cx| { - state.push(event, metadata); - }); - } - Signal::Metadata(public_key) => { - let metadata = async_cx - .background_executor() - .spawn(async move { - client - .database() - .metadata(public_key) - .await - .unwrap_or_default() - }) - .await; - - _ = async_cx.update_global::(|state, _cx| { - state.seen(public_key, metadata); + state.new_message(event, metadata) }); } } diff --git a/crates/app/src/states/chat.rs b/crates/app/src/states/chat.rs index 7fc4c90..ef69e67 100644 --- a/crates/app/src/states/chat.rs +++ b/crates/app/src/states/chat.rs @@ -1,11 +1,15 @@ -use gpui::*; +use crate::get_client; +use crate::utils::get_room_id; +use gpui::{AppContext, Context, Global, Model, SharedString}; +use itertools::Itertools; use nostr_sdk::prelude::*; use rnglib::{Language, RNG}; use serde::Deserialize; -use std::sync::{Arc, RwLock}; - -use super::metadata::MetadataRegistry; -use crate::utils::get_room_id; +use std::{ + cmp::Reverse, + collections::HashMap, + sync::{Arc, RwLock}, +}; #[derive(Clone, PartialEq, Eq, Deserialize)] pub struct Room { @@ -15,23 +19,17 @@ pub struct Room { pub last_seen: Timestamp, pub title: Option, pub metadata: Option, - is_initialized: bool, } impl Room { pub fn new( + id: SharedString, owner: PublicKey, members: Vec, last_seen: Timestamp, title: Option, - cx: &mut WindowContext<'_>, + metadata: Option, ) -> Self { - // Get unique id based on members - let id = get_room_id(&owner, &members).into(); - - // Get metadata for all members if exists - let metadata = cx.global::().get(&owner); - Self { id, title, @@ -39,16 +37,17 @@ impl Room { last_seen, owner, metadata, - is_initialized: false, } } - pub fn parse(event: &Event, cx: &mut WindowContext<'_>) -> Self { + pub fn parse(event: &Event, metadata: Option) -> Self { let owner = event.pubkey; let last_seen = event.created_at; + let id = SharedString::from(get_room_id(&owner, &event.tags)); // Get all members from event's tag - let members: Vec = event.tags.public_keys().copied().collect(); + let mut members: Vec = event.tags.public_keys().copied().collect(); + members.push(owner); // Get title from event's tag let title = if let Some(tag) = event.tags.find(TagKind::Title) { @@ -60,20 +59,26 @@ impl Room { Some(name.into()) }; - Self::new(owner, members, last_seen, title, cx) + Self::new(id, owner, members, last_seen, title, metadata) } } #[derive(Clone, Debug)] pub struct Message { - pub room_id: SharedString, pub event: Event, pub metadata: Option, } +impl Message { + pub fn new(event: Event, metadata: Option) -> Self { + // TODO: parse event's content + Self { event, metadata } + } +} + pub struct ChatRegistry { - pub new_messages: Arc>>, - pub reload: bool, + pub messages: RwLock>>>>, + pub rooms: Model>, pub is_initialized: bool, } @@ -81,34 +86,90 @@ impl Global for ChatRegistry {} impl ChatRegistry { pub fn set_global(cx: &mut AppContext) { - cx.set_global(Self::new()); - } + let rooms = cx.new_model(|_| Vec::new()); + let messages = RwLock::new(HashMap::new()); - pub fn update(&mut self) { - if !self.is_initialized { - self.is_initialized = true; - } else { - self.reload = true; - } - } - - pub fn push(&mut self, event: Event, metadata: Option) { - let pubkeys: Vec = event.tags.public_keys().copied().collect(); - let room_id = get_room_id(&event.pubkey, &pubkeys); - let message = Message { - room_id: room_id.into(), - event, - metadata, - }; - - self.new_messages.write().unwrap().push(message); - } - - fn new() -> Self { - Self { - new_messages: Arc::new(RwLock::new(Vec::new())), - reload: false, + cx.set_global(Self { + messages, + rooms, is_initialized: false, + }); + } + + pub fn init(&mut self, cx: &mut AppContext) { + if self.is_initialized { + return; } + + let async_cx = cx.to_async(); + // Get all current room's ids + let ids: Vec = self + .rooms + .read(cx) + .iter() + .map(|ev| get_room_id(&ev.pubkey, &ev.tags)) + .collect(); + + cx.foreground_executor() + .spawn(async move { + let client = get_client(); + let signer = client.signer().await.unwrap(); + let public_key = signer.get_public_key().await.unwrap(); + + let filter = Filter::new() + .kind(Kind::PrivateDirectMessage) + .pubkey(public_key); + + let events = async_cx + .background_executor() + .spawn(async move { + if let Ok(events) = client.database().query(vec![filter]).await { + events + .into_iter() + .filter(|ev| ev.pubkey != public_key) + .filter(|ev| { + let new_id = get_room_id(&ev.pubkey, &ev.tags); + // Get new events only + !ids.iter().any(|id| id == &new_id) + }) // Filter all messages from current user + .unique_by(|ev| ev.pubkey) + .sorted_by_key(|ev| Reverse(ev.created_at)) + .collect::>() + } else { + Vec::new() + } + }) + .await; + + _ = async_cx.update_global::(|state, cx| { + state.rooms.update(cx, |model, cx| { + model.extend(events); + cx.notify(); + }); + + state.is_initialized = true; + }); + }) + .detach(); + } + + pub fn new_message(&mut self, event: Event, metadata: Option) { + // Get room id + let room_id = SharedString::from(get_room_id(&event.pubkey, &event.tags)); + // Create message + let message = Message::new(event, metadata); + + self.messages + .write() + .unwrap() + .entry(room_id) + .or_insert(Arc::new(RwLock::new(Vec::new()))) + .write() + .unwrap() + .push(message) + } + + pub fn get_messages(&self, id: &SharedString) -> Option>>> { + self.messages.read().unwrap().get(id).cloned() } } diff --git a/crates/app/src/states/metadata.rs b/crates/app/src/states/metadata.rs deleted file mode 100644 index 020e42e..0000000 --- a/crates/app/src/states/metadata.rs +++ /dev/null @@ -1,43 +0,0 @@ -use gpui::*; -use nostr_sdk::prelude::*; -use std::{ - collections::HashMap, - sync::{Arc, Mutex, RwLock}, -}; - -pub struct MetadataRegistry { - seens: Arc>>, - profiles: Arc>>, -} - -impl Global for MetadataRegistry {} - -impl MetadataRegistry { - pub fn set_global(cx: &mut AppContext) { - cx.set_global(Self::new()); - } - - pub fn seen(&mut self, public_key: PublicKey, metadata: Option) { - let mut seens = self.seens.lock().unwrap(); - - if !seens.contains(&public_key) { - seens.push(public_key); - drop(seens); - - if let Some(metadata) = metadata { - self.profiles.write().unwrap().insert(public_key, metadata); - } - } - } - - pub fn get(&self, public_key: &PublicKey) -> Option { - self.profiles.read().unwrap().get(public_key).cloned() - } - - fn new() -> Self { - let seens = Arc::new(Mutex::new(Vec::new())); - let profiles = Arc::new(RwLock::new(HashMap::new())); - - Self { seens, profiles } - } -} diff --git a/crates/app/src/states/mod.rs b/crates/app/src/states/mod.rs index 59fb811..d0f897b 100644 --- a/crates/app/src/states/mod.rs +++ b/crates/app/src/states/mod.rs @@ -1,4 +1,2 @@ pub mod account; pub mod chat; -pub mod metadata; -pub mod signal; diff --git a/crates/app/src/states/signal.rs b/crates/app/src/states/signal.rs deleted file mode 100644 index 6a5c7f3..0000000 --- a/crates/app/src/states/signal.rs +++ /dev/null @@ -1,30 +0,0 @@ -use gpui::*; -use nostr_sdk::prelude::*; -use std::sync::Arc; -use tokio::sync::mpsc::UnboundedSender; - -#[derive(Clone)] -pub enum Signal { - /// Receive metadata - Metadata(PublicKey), - /// Receive event - Event(Event), - /// Receive EOSE - Eose, -} - -pub struct SignalRegistry { - pub tx: Arc>, -} - -impl Global for SignalRegistry {} - -impl SignalRegistry { - pub fn set_global(cx: &mut AppContext, tx: UnboundedSender) { - cx.set_global(Self::new(tx)); - } - - fn new(tx: UnboundedSender) -> Self { - Self { tx: Arc::new(tx) } - } -} diff --git a/crates/app/src/utils.rs b/crates/app/src/utils.rs index f4a9bdb..4abbb79 100644 --- a/crates/app/src/utils.rs +++ b/crates/app/src/utils.rs @@ -1,8 +1,13 @@ use chrono::{Duration, Local, TimeZone}; use nostr_sdk::prelude::*; -pub fn get_room_id(owner: &PublicKey, public_keys: &[PublicKey]) -> String { - let hex: Vec = public_keys +pub fn get_room_id(author: &PublicKey, tags: &Tags) -> String { + // Get all public keys + let mut pubkeys: Vec = tags.public_keys().copied().collect(); + // Add author to public keys list + pubkeys.insert(0, *author); + + let hex: Vec = pubkeys .iter() .map(|m| { let hex = m.to_hex(); @@ -11,9 +16,8 @@ pub fn get_room_id(owner: &PublicKey, public_keys: &[PublicKey]) -> String { split.to_owned() }) .collect(); - let mems = hex.join("-"); - format!("{}-{}", &owner.to_hex()[..6], mems) + hex.join("-") } pub fn show_npub(public_key: PublicKey, len: usize) -> String { diff --git a/crates/app/src/views/account.rs b/crates/app/src/views/account.rs index 785c70f..8c968dc 100644 --- a/crates/app/src/views/account.rs +++ b/crates/app/src/views/account.rs @@ -1,21 +1,21 @@ -use gpui::*; +use gpui::prelude::FluentBuilder; +use gpui::{ + actions, img, Context, IntoElement, Model, ObjectFit, ParentElement, Render, Styled, + StyledImage, ViewContext, +}; use nostr_sdk::prelude::*; -use prelude::FluentBuilder; use ui::{ button::{Button, ButtonVariants}, popup_menu::PopupMenuExt, Icon, IconName, Sizable, }; -use crate::{ - constants::IMAGE_SERVICE, - get_client, - states::{metadata::MetadataRegistry, signal::SignalRegistry}, -}; +use crate::{constants::IMAGE_SERVICE, get_client}; actions!(account, [ToDo]); pub struct Account { + #[allow(dead_code)] public_key: PublicKey, metadata: Model>, } @@ -23,25 +23,8 @@ pub struct Account { impl Account { pub fn new(public_key: PublicKey, cx: &mut ViewContext<'_, Self>) -> Self { let metadata = cx.new_model(|_| None); + let async_metadata = metadata.clone(); - // Request metadata - _ = cx.global::().tx.send(public_key); - - // Reload when received metadata - cx.observe_global::(|chat, cx| { - chat.load_metadata(cx); - }) - .detach(); - - Self { - public_key, - metadata, - } - } - - pub fn load_metadata(&mut self, cx: &mut ViewContext) { - let public_key = self.public_key; - let async_metadata = self.metadata.clone(); let mut async_cx = cx.to_async(); cx.foreground_executor() @@ -60,6 +43,11 @@ impl Account { }; }) .detach(); + + Self { + public_key, + metadata, + } } } diff --git a/crates/app/src/views/app.rs b/crates/app/src/views/app.rs index 7079445..84aea2d 100644 --- a/crates/app/src/views/app.rs +++ b/crates/app/src/views/app.rs @@ -1,5 +1,13 @@ -use gpui::*; -use prelude::FluentBuilder; +use super::{ + account::Account, chat::ChatPanel, onboarding::Onboarding, sidebar::Sidebar, + welcome::WelcomePanel, +}; +use crate::states::{account::AccountRegistry, chat::Room}; +use gpui::prelude::FluentBuilder; +use gpui::{ + div, impl_actions, px, Axis, Context, Edges, InteractiveElement, IntoElement, Model, + ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, WindowContext, +}; use serde::Deserialize; use std::sync::Arc; use ui::{ @@ -9,16 +17,9 @@ use ui::{ Root, Sizable, TitleBar, }; -use super::{ - account::Account, chat::ChatPanel, contact::ContactPanel, onboarding::Onboarding, - sidebar::Sidebar, welcome::WelcomePanel, -}; -use crate::states::{account::AccountRegistry, chat::Room}; - #[derive(Clone, PartialEq, Eq, Deserialize)] pub enum PanelKind { Room(Arc), - Contact, } #[derive(Clone, PartialEq, Eq, Deserialize)] @@ -125,13 +126,6 @@ impl AppView { PanelKind::Room(room) => { let panel = Arc::new(ChatPanel::new(room, cx)); - self.dock.update(cx, |dock_area, cx| { - dock_area.add_panel(panel, action.position, cx); - }); - } - PanelKind::Contact => { - let panel = Arc::new(ContactPanel::new(cx)); - self.dock.update(cx, |dock_area, cx| { dock_area.add_panel(panel, action.position, cx); }); diff --git a/crates/app/src/views/chat/message.rs b/crates/app/src/views/chat/message.rs index 546e468..25a23ad 100644 --- a/crates/app/src/views/chat/message.rs +++ b/crates/app/src/views/chat/message.rs @@ -1,6 +1,8 @@ -use gpui::*; +use gpui::{ + div, img, prelude::FluentBuilder, InteractiveElement, IntoElement, ParentElement, RenderOnce, + SharedString, Styled, WindowContext, +}; use nostr_sdk::prelude::*; -use prelude::FluentBuilder; use ui::{theme::ActiveTheme, StyledExt}; use crate::{ diff --git a/crates/app/src/views/chat/mod.rs b/crates/app/src/views/chat/mod.rs index d534b75..1b3d0f4 100644 --- a/crates/app/src/views/chat/mod.rs +++ b/crates/app/src/views/chat/mod.rs @@ -1,8 +1,10 @@ -use std::sync::Arc; - -use gpui::*; +use gpui::{ + div, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, IntoElement, + ParentElement, Render, SharedString, Styled, View, VisualContext, WindowContext, +}; use nostr_sdk::prelude::*; use room::RoomPanel; +use std::sync::Arc; use ui::{ button::Button, dock::{Panel, PanelEvent, PanelState}, @@ -91,7 +93,7 @@ impl Panel for ChatPanel { impl EventEmitter for ChatPanel {} impl FocusableView for ChatPanel { - fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle { + fn focus_handle(&self, _: &AppContext) -> FocusHandle { self.focus_handle.clone() } } diff --git a/crates/app/src/views/chat/room.rs b/crates/app/src/views/chat/room.rs index dd1c805..c7f32b4 100644 --- a/crates/app/src/views/chat/room.rs +++ b/crates/app/src/views/chat/room.rs @@ -1,4 +1,7 @@ -use gpui::*; +use gpui::{ + div, list, px, Context, Flatten, IntoElement, ListAlignment, ListState, Model, ParentElement, + PathPromptOptions, Pixels, Render, SharedString, Styled, View, ViewContext, VisualContext, +}; use itertools::Itertools; use nostr_sdk::prelude::*; use std::sync::Arc; @@ -15,7 +18,6 @@ use crate::{ states::{ account::AccountRegistry, chat::{ChatRegistry, Room}, - metadata::MetadataRegistry, }, }; @@ -122,21 +124,34 @@ impl RoomPanel { .await; if let Ok(events) = events { - let items: Vec = events - .into_iter() - .sorted_by_key(|ev| ev.created_at) - .map(|ev| { - // Get user's metadata - let metadata = async_cx - .read_global::(|state, _cx| { - state.get(&ev.pubkey) - }) - .unwrap(); + let mut items: Vec = Vec::new(); - // Return message item - RoomMessage::new(ev.pubkey, metadata, ev.content, ev.created_at) - }) - .collect(); + for event in events.into_iter().sorted_by_key(|ev| ev.created_at) { + let metadata = async_cx + .background_executor() + .spawn( + async move { client.database().metadata(event.pubkey).await }, + ) + .await; + + let message = if let Ok(metadata) = metadata { + RoomMessage::new( + event.pubkey, + metadata, + event.content, + event.created_at, + ) + } else { + RoomMessage::new( + event.pubkey, + None, + event.content, + event.created_at, + ) + }; + + items.push(message); + } let total = items.len(); @@ -154,38 +169,38 @@ impl RoomPanel { pub fn subscribe(&self, cx: &mut ViewContext) { let room_id = self.id.clone(); let messages = self.messages.clone(); - let current_user = cx.global::().get().unwrap(); cx.observe_global::(move |_, cx| { let state = cx.global::(); - let new_messages = state.new_messages.read().unwrap().clone(); - let filter = new_messages - .into_iter() - .filter(|m| m.room_id == room_id && m.event.pubkey != current_user) - .collect::>(); + let new_messages = state.get_messages(&room_id); - let items: Vec = filter - .into_iter() - .map(|m| { - RoomMessage::new( - m.event.pubkey, - m.metadata, - m.event.content, - m.event.created_at, - ) - }) - .collect(); + if let Some(new_messages) = new_messages { + let items: Vec = new_messages + .read() + .unwrap() + .clone() + .into_iter() + .map(|m| { + RoomMessage::new( + m.event.pubkey, + m.metadata, + m.event.content, + m.event.created_at, + ) + }) + .collect(); - cx.update_model(&messages, |model, cx| { - model.items.extend(items); - model.count = model.items.len(); - cx.notify(); - }); + cx.update_model(&messages, |model, cx| { + model.items.extend(items); + model.count = model.items.len(); + cx.notify(); + }); + } }) .detach(); } - pub fn send_message(&mut self, cx: &mut ViewContext) { + fn send_message(&mut self, cx: &mut ViewContext) { let owner = self.owner; let current_user = cx.global::().get().unwrap(); let content = self.input.read(cx).text().to_string(); @@ -252,7 +267,7 @@ impl RoomPanel { } impl Render for RoomPanel { - fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_flex() .size_full() .child(list(self.list.clone()).flex_1()) diff --git a/crates/app/src/views/contact/item.rs b/crates/app/src/views/contact/item.rs deleted file mode 100644 index 15021b1..0000000 --- a/crates/app/src/views/contact/item.rs +++ /dev/null @@ -1,113 +0,0 @@ -use gpui::*; -use nostr_sdk::prelude::*; -use prelude::FluentBuilder; -use ui::theme::ActiveTheme; - -use crate::{ - constants::IMAGE_SERVICE, - get_client, - states::{metadata::MetadataRegistry, signal::SignalRegistry}, - utils::show_npub, -}; - -pub struct ContactListItem { - public_key: PublicKey, - metadata: Model>, -} - -impl ContactListItem { - pub fn new(public_key: PublicKey, cx: &mut ViewContext<'_, Self>) -> Self { - let metadata = cx.new_model(|_| None); - - // Request metadata - _ = cx.global::().tx.send(public_key); - - // Reload when received metadata - cx.observe_global::(|item, cx| { - item.load_metadata(cx); - }) - .detach(); - - Self { - public_key, - metadata, - } - } - - pub fn load_metadata(&mut self, cx: &mut ViewContext) { - let public_key = self.public_key; - let async_metadata = self.metadata.clone(); - let mut async_cx = cx.to_async(); - - cx.foreground_executor() - .spawn({ - let client = get_client(); - - async move { - let query = async_cx - .background_executor() - .spawn(async move { client.database().metadata(public_key).await }) - .await; - - if let Ok(metadata) = query { - _ = async_cx.update_model(&async_metadata, |a, b| { - *a = metadata; - b.notify(); - }); - }; - } - }) - .detach(); - } -} - -impl Render for ContactListItem { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let fallback = show_npub(self.public_key, 16); - let mut content = div() - .w_full() - .h_10() - .px_2() - .flex() - .items_center() - .gap_2() - .text_sm(); - - if let Some(metadata) = self.metadata.read(cx).as_ref() { - content = content - .map(|this| { - if let Some(picture) = metadata.picture.clone() { - this.flex_shrink_0().child( - img(format!( - "{}/?url={}&w=72&h=72&fit=cover&mask=circle&n=-1", - IMAGE_SERVICE, picture - )) - .size_8(), - ) - } else { - this.flex_shrink_0() - .child(img("brand/avatar.png").size_8().rounded_full()) - } - }) - .map(|this| { - if let Some(display_name) = metadata.display_name.clone() { - this.flex_1().child(display_name) - } else { - this.flex_1().child(fallback) - } - }) - } else { - content = content - .child(img("brand/avatar.png").size_8().rounded_full()) - .child(fallback) - } - - div() - .rounded_md() - .hover(|this| { - this.bg(cx.theme().muted) - .text_color(cx.theme().muted_foreground) - }) - .child(content) - } -} diff --git a/crates/app/src/views/contact/mod.rs b/crates/app/src/views/contact/mod.rs deleted file mode 100644 index f2fa19d..0000000 --- a/crates/app/src/views/contact/mod.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::time::Duration; - -use gpui::*; -use item::ContactListItem; -use prelude::FluentBuilder; -use ui::{ - button::Button, - dock::{Panel, PanelEvent, PanelState}, - indicator::Indicator, - popup_menu::PopupMenu, - scroll::ScrollbarAxis, - theme::ActiveTheme, - v_flex, Sizable, StyledExt, -}; - -use crate::get_client; - -mod item; - -pub struct ContactPanel { - name: SharedString, - closeable: bool, - zoomable: bool, - focus_handle: FocusHandle, - // Contacts - view_id: EntityId, - contacts: Model>>>, -} - -impl ContactPanel { - pub fn new(cx: &mut WindowContext) -> View { - cx.new_view(Self::view) - } - - fn view(cx: &mut ViewContext) -> Self { - let contacts = cx.new_model(|_| None); - let async_contacts = contacts.clone(); - - let mut async_cx = cx.to_async(); - - cx.foreground_executor() - .spawn({ - let client = get_client(); - - async move { - if let Ok(contacts) = async_cx - .background_executor() - .spawn(async move { client.get_contact_list(Duration::from_secs(3)).await }) - .await - { - let views: Vec> = contacts - .into_iter() - .map(|contact| { - async_cx - .new_view(|cx| ContactListItem::new(contact.public_key, cx)) - .unwrap() - }) - .collect(); - - _ = async_cx.update_model(&async_contacts, |model, cx| { - *model = Some(views); - cx.notify(); - }); - } - } - }) - .detach(); - - Self { - name: "Contacts".into(), - closeable: true, - zoomable: true, - focus_handle: cx.focus_handle(), - view_id: cx.entity_id(), - contacts, - } - } -} - -impl Panel for ContactPanel { - fn panel_id(&self) -> SharedString { - "Contact".into() - } - - fn title(&self, _cx: &WindowContext) -> AnyElement { - self.name.clone().into_any_element() - } - - fn closeable(&self, _cx: &WindowContext) -> bool { - self.closeable - } - - fn zoomable(&self, _cx: &WindowContext) -> bool { - self.zoomable - } - - fn popup_menu(&self, menu: PopupMenu, _cx: &WindowContext) -> PopupMenu { - menu.track_focus(&self.focus_handle) - } - - fn toolbar_buttons(&self, _cx: &WindowContext) -> Vec