From 7fd9f22b4aca2c1caa631a8ca5f6612bbc0c45e8 Mon Sep 17 00:00:00 2001 From: reya Date: Thu, 26 Dec 2024 12:55:51 +0700 Subject: [PATCH] wip: refactor --- crates/app/src/main.rs | 20 ++- crates/app/src/states/chat.rs | 10 +- crates/app/src/states/metadata.rs | 32 ++++- crates/app/src/views/dock/chat/message.rs | 104 ++++++++++++++++ crates/app/src/views/dock/chat/mod.rs | 1 + crates/app/src/views/dock/chat/room.rs | 142 ++++------------------ crates/app/src/views/dock/inbox/item.rs | 1 - crates/app/src/views/dock/inbox/mod.rs | 19 ++- 8 files changed, 190 insertions(+), 139 deletions(-) create mode 100644 crates/app/src/views/dock/chat/message.rs diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index eed7ec7..5720ea7 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -250,18 +250,32 @@ async fn main() { let metadata = async_cx .background_executor() .spawn(async move { - (client.database().metadata(event.pubkey).await) + client + .database() + .metadata(event.pubkey) + .await .unwrap_or_default() }) .await; - _ = async_cx.update_global::(|state, _| { + _ = async_cx.update_global::(|state, _cx| { state.push(event, metadata); }); } Signal::RecvMetadata(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); + state.seen(public_key, metadata); }) } _ => {} diff --git a/crates/app/src/states/chat.rs b/crates/app/src/states/chat.rs index 09f3c18..4159d25 100644 --- a/crates/app/src/states/chat.rs +++ b/crates/app/src/states/chat.rs @@ -1,6 +1,7 @@ use gpui::*; use nostr_sdk::prelude::*; use serde::Deserialize; +use std::sync::{Arc, RwLock}; use crate::utils::get_room_id; @@ -47,7 +48,7 @@ pub struct Message { } pub struct ChatRegistry { - pub new_messages: Vec, + pub new_messages: Arc>>, pub reload: bool, pub is_initialized: bool, } @@ -68,12 +69,15 @@ impl ChatRegistry { } pub fn push(&mut self, event: Event, metadata: Option) { - self.new_messages.push(Message { event, metadata }); + self.new_messages + .write() + .unwrap() + .push(Message { event, metadata }); } fn new() -> Self { Self { - new_messages: Vec::new(), + new_messages: Arc::new(RwLock::new(Vec::new())), reload: false, is_initialized: false, } diff --git a/crates/app/src/states/metadata.rs b/crates/app/src/states/metadata.rs index 3189cea..0dd9bd9 100644 --- a/crates/app/src/states/metadata.rs +++ b/crates/app/src/states/metadata.rs @@ -1,8 +1,13 @@ use gpui::*; use nostr_sdk::prelude::*; +use std::{ + collections::HashMap, + sync::{Arc, Mutex, RwLock}, +}; pub struct MetadataRegistry { - seens: Vec, + seens: Arc>>, + profiles: Arc>>, } impl Global for MetadataRegistry {} @@ -13,16 +18,31 @@ impl MetadataRegistry { } pub fn contains(&self, public_key: PublicKey) -> bool { - self.seens.contains(&public_key) + self.seens.lock().unwrap().contains(&public_key) } - pub fn seen(&mut self, public_key: PublicKey) { - if !self.seens.contains(&public_key) { - self.seens.push(public_key); + 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 { - Self { seens: Vec::new() } + 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/views/dock/chat/message.rs b/crates/app/src/views/dock/chat/message.rs new file mode 100644 index 0000000..ca4e8f3 --- /dev/null +++ b/crates/app/src/views/dock/chat/message.rs @@ -0,0 +1,104 @@ +use coop_ui::{theme::ActiveTheme, StyledExt}; +use gpui::*; +use nostr_sdk::prelude::*; +use prelude::FluentBuilder; + +use crate::{ + constants::IMAGE_SERVICE, + utils::{ago, show_npub}, +}; + +#[derive(Clone, Debug, IntoElement)] +pub struct RoomMessage { + #[allow(dead_code)] + author: PublicKey, + fallback: SharedString, + metadata: Option, + content: SharedString, + created_at: SharedString, +} + +impl RoomMessage { + pub fn new( + author: PublicKey, + metadata: Option, + content: String, + created_at: Timestamp, + ) -> Self { + let created_at = ago(created_at.as_u64()).into(); + let fallback = show_npub(author, 16).into(); + + Self { + author, + metadata, + fallback, + created_at, + content: content.into(), + } + } +} + +impl RenderOnce for RoomMessage { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + div() + .flex() + .gap_3() + .w_full() + .p_2() + .child(div().flex_shrink_0().map(|this| { + if let Some(metadata) = self.metadata.clone() { + if let Some(picture) = metadata.picture { + this.child( + img(format!( + "{}/?url={}&w=100&h=100&n=-1", + IMAGE_SERVICE, picture + )) + .size_8() + .rounded_full() + .object_fit(ObjectFit::Cover), + ) + } else { + this.child(img("brand/avatar.png").size_8().rounded_full()) + } + } else { + this.child(img("brand/avatar.png").size_8().rounded_full()) + } + })) + .child( + div() + .flex() + .flex_col() + .flex_initial() + .overflow_hidden() + .child( + div() + .flex() + .items_baseline() + .gap_2() + .text_xs() + .child(div().font_semibold().map(|this| { + if let Some(metadata) = self.metadata { + if let Some(display_name) = metadata.display_name { + this.child(display_name) + } else { + this.child(self.fallback) + } + } else { + this.child(self.fallback) + } + })) + .child( + div() + .child(self.created_at) + .text_color(cx.theme().muted_foreground), + ), + ) + .child( + div() + .text_sm() + .text_color(cx.theme().foreground) + .child(self.content), + ), + ) + } +} diff --git a/crates/app/src/views/dock/chat/mod.rs b/crates/app/src/views/dock/chat/mod.rs index f9fda8f..38a1db1 100644 --- a/crates/app/src/views/dock/chat/mod.rs +++ b/crates/app/src/views/dock/chat/mod.rs @@ -9,6 +9,7 @@ use std::sync::Arc; use crate::states::chat::Room; +mod message; mod room; pub struct ChatPanel { diff --git a/crates/app/src/views/dock/chat/room.rs b/crates/app/src/views/dock/chat/room.rs index 3e8fbe7..76b4280 100644 --- a/crates/app/src/views/dock/chat/room.rs +++ b/crates/app/src/views/dock/chat/room.rs @@ -2,109 +2,26 @@ use coop_ui::{ button::{Button, ButtonVariants}, input::{InputEvent, TextInput}, theme::ActiveTheme, - v_flex, Icon, IconName, StyledExt, + v_flex, Icon, IconName, }; use gpui::*; use itertools::Itertools; use nostr_sdk::prelude::*; -use prelude::FluentBuilder; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; +use super::message::RoomMessage; use crate::{ get_client, - states::chat::{ChatRegistry, Room}, - utils::{ago, show_npub}, + states::{ + chat::{ChatRegistry, Room}, + metadata::MetadataRegistry, + }, }; -#[derive(Clone, Debug, IntoElement)] -pub struct MessageItem { - author: PublicKey, - metadata: Option, - content: SharedString, - created_at: Timestamp, -} - -impl MessageItem { - pub fn new( - author: PublicKey, - metadata: Option, - content: String, - created_at: Timestamp, - ) -> Self { - MessageItem { - author, - metadata, - created_at, - content: content.into(), - } - } -} - -impl RenderOnce for MessageItem { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - let ago = ago(self.created_at.as_u64()); - let fallback_name = show_npub(self.author, 16); - - div() - .flex() - .gap_3() - .w_full() - .p_2() - .child(div().flex_shrink_0().map(|this| { - if let Some(metadata) = self.metadata.clone() { - if let Some(picture) = metadata.picture { - this.child( - img(picture) - .size_8() - .rounded_full() - .object_fit(ObjectFit::Cover), - ) - } else { - this.child(img("brand/avatar.png").size_8().rounded_full()) - } - } else { - this.child(img("brand/avatar.png").size_8().rounded_full()) - } - })) - .child( - div() - .flex() - .flex_col() - .flex_initial() - .overflow_hidden() - .child( - div() - .flex() - .items_baseline() - .gap_2() - .text_xs() - .child(div().font_semibold().map(|this| { - if let Some(metadata) = self.metadata { - if let Some(display_name) = metadata.display_name { - this.child(display_name) - } else { - this.child(fallback_name) - } - } else { - this.child(fallback_name) - } - })) - .child(div().child(ago).text_color(cx.theme().muted_foreground)), - ) - .child( - div() - .text_sm() - .text_color(cx.theme().foreground) - .child(self.content), - ), - ) - } -} - #[derive(Clone)] pub struct Messages { count: usize, - items: Vec, + items: Vec, } pub struct ChatRoom { @@ -201,37 +118,19 @@ impl ChatRoom { .await; if let Ok(events) = events { - let public_keys: Vec = events - .iter() - .unique_by(|ev| ev.pubkey) - .map(|ev| ev.pubkey) - .collect(); - - let mut profiles = async_cx - .background_executor() - .spawn(async move { - let mut data: HashMap> = HashMap::new(); - - for public_key in public_keys.into_iter() { - if let Ok(metadata) = - client.database().metadata(public_key).await - { - data.insert(public_key, metadata); - } - } - - data - }) - .await; - - let items: Vec = events + let items: Vec = events .into_iter() .sorted_by_key(|ev| ev.created_at) .map(|ev| { // Get user's metadata - let metadata = profiles.get_mut(&ev.pubkey).and_then(Option::take); + let metadata = async_cx + .read_global::(|state, _cx| { + state.get(ev.pubkey) + }) + .unwrap(); + // Return message item - MessageItem::new(ev.pubkey, metadata, ev.content, ev.created_at) + RoomMessage::new(ev.pubkey, metadata, ev.content, ev.created_at) }) .collect(); @@ -253,14 +152,17 @@ impl ChatRoom { cx.observe_global::(move |_, cx| { let state = cx.global::(); - let events = state.new_messages.clone(); // let mut metadata = state.metadata.clone(); // TODO: filter messages - let items: Vec = events + let items: Vec = state + .new_messages + .read() + .unwrap() + .clone() .into_iter() .map(|m| { - MessageItem::new( + RoomMessage::new( m.event.pubkey, m.metadata, m.event.content, diff --git a/crates/app/src/views/dock/inbox/item.rs b/crates/app/src/views/dock/inbox/item.rs index c725e16..a0f5596 100644 --- a/crates/app/src/views/dock/inbox/item.rs +++ b/crates/app/src/views/dock/inbox/item.rs @@ -70,7 +70,6 @@ impl InboxItem { } pub fn action(&self, cx: &mut WindowContext<'_>) { - println!("Test"); let room = Arc::new(Room::new(&self.event)); cx.dispatch_action(Box::new(AddPanel { diff --git a/crates/app/src/views/dock/inbox/mod.rs b/crates/app/src/views/dock/inbox/mod.rs index b05fd87..66cbf7c 100644 --- a/crates/app/src/views/dock/inbox/mod.rs +++ b/crates/app/src/views/dock/inbox/mod.rs @@ -25,21 +25,23 @@ impl Inbox { cx.observe_global::(|this, cx| { let state = cx.global::(); + let empty_messages = state.new_messages.read().unwrap().is_empty(); - if state.reload || (state.is_initialized && state.new_messages.is_empty()) { + if state.reload || (state.is_initialized && empty_messages) { this.load(cx); } else { #[allow(clippy::collapsible_if)] if let Some(items) = this.items.read(cx).as_ref() { - // Get all new messages - let new_messages = state.new_messages.clone(); - // Get all current chats let current_rooms: Vec = items.iter().map(|item| item.model.read(cx).id()).collect(); - // Create view for new chats only - let new = new_messages + // Get all new messages + let messages = state + .new_messages + .read() + .unwrap() + .clone() .into_iter() .filter(|m| { let keys = m.event.tags.public_keys().copied().collect::>(); @@ -49,6 +51,11 @@ impl Inbox { !current_rooms.iter().any(|id| id == &new_id) }) + .collect::>(); + + // Create view for new chats only + let new = messages + .into_iter() .map(|m| cx.new_view(|cx| InboxItem::new(m.event, cx))) .collect::>();