From ef839102d498accafca72af19283ae7e50ddb7a8 Mon Sep 17 00:00:00 2001 From: reya Date: Sat, 11 Jan 2025 15:40:35 +0700 Subject: [PATCH] wip: refactor --- crates/app/src/states/chat/mod.rs | 8 + crates/app/src/states/chat/room.rs | 3 + crates/app/src/utils.rs | 15 +- crates/app/src/views/app.rs | 21 ++- crates/app/src/views/chat/mod.rs | 57 +++++-- crates/app/src/views/mod.rs | 2 +- crates/app/src/views/sidebar/inbox.rs | 227 +++++++++++--------------- 7 files changed, 175 insertions(+), 158 deletions(-) diff --git a/crates/app/src/states/chat/mod.rs b/crates/app/src/states/chat/mod.rs index 60181fc..b656b88 100644 --- a/crates/app/src/states/chat/mod.rs +++ b/crates/app/src/states/chat/mod.rs @@ -150,6 +150,14 @@ impl ChatRegistry { self.inbox.downgrade() } + pub fn room(&self, id: &u64, cx: &AppContext) -> Option> { + self.inbox + .read(cx) + .iter() + .find(|model| &model.read(cx).id == id) + .map(|model| model.downgrade()) + } + pub fn new_messages(&self) -> WeakModel { self.new_messages.downgrade() } diff --git a/crates/app/src/states/chat/room.rs b/crates/app/src/states/chat/room.rs index 373754a..4eb871c 100644 --- a/crates/app/src/states/chat/room.rs +++ b/crates/app/src/states/chat/room.rs @@ -50,6 +50,7 @@ impl Member { pub struct Room { pub id: u64, pub title: Option, + pub owner: PublicKey, pub members: Vec, pub last_seen: Timestamp, pub is_group: bool, @@ -60,6 +61,7 @@ impl Room { let id = room_hash(&event.tags); let last_seen = event.created_at; + let owner = event.pubkey; let members: Vec = event .tags .public_keys() @@ -80,6 +82,7 @@ impl Room { Self { id, + owner, members, title, last_seen, diff --git a/crates/app/src/utils.rs b/crates/app/src/utils.rs index efdc3c6..1a5b185 100644 --- a/crates/app/src/utils.rs +++ b/crates/app/src/utils.rs @@ -1,6 +1,9 @@ use chrono::{Duration, Local, TimeZone}; use nostr_sdk::prelude::*; -use std::hash::{DefaultHasher, Hash, Hasher}; +use std::{ + collections::HashSet, + hash::{DefaultHasher, Hash, Hasher}, +}; pub fn room_hash(tags: &Tags) -> u64 { let pubkeys: Vec = tags.public_keys().copied().collect(); @@ -11,6 +14,16 @@ pub fn room_hash(tags: &Tags) -> u64 { hasher.finish() } +pub fn compare(a: &[T], b: &[T]) -> bool +where + T: Eq + Hash, +{ + let a: HashSet<_> = a.iter().collect(); + let b: HashSet<_> = b.iter().collect(); + + a == b +} + pub fn shorted_public_key(public_key: PublicKey) -> String { let pk = public_key.to_string(); format!("{}:{}", &pk[0..4], &pk[pk.len() - 4..]) diff --git a/crates/app/src/views/app.rs b/crates/app/src/views/app.rs index ed225ea..7c8261a 100644 --- a/crates/app/src/views/app.rs +++ b/crates/app/src/views/app.rs @@ -1,5 +1,8 @@ -use super::{account::Account, onboarding::Onboarding, sidebar::Sidebar, welcome::WelcomePanel}; -use crate::states::app::AppRegistry; +use super::{ + account::Account, chat::ChatPanel, onboarding::Onboarding, sidebar::Sidebar, + welcome::WelcomePanel, +}; +use crate::states::{app::AppRegistry, chat::ChatRegistry}; use gpui::{ div, impl_actions, px, Axis, Context, Edges, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, WindowContext, @@ -129,17 +132,19 @@ impl AppView { } fn on_action_add_panel(&mut self, action: &AddPanel, cx: &mut ViewContext) { - /* match &action.panel { PanelKind::Room(id) => { - let panel = Arc::new(ChatPanel::new(id, cx)); + if let Some(weak_room) = cx.global::().room(id, cx) { + if let Some(room) = weak_room.upgrade() { + let panel = Arc::new(ChatPanel::new(room, cx)); - self.dock.update(cx, |dock_area, cx| { - dock_area.add_panel(panel, action.position, cx); - }); + self.dock.update(cx, |dock_area, cx| { + dock_area.add_panel(panel, action.position, cx); + }); + } + } } }; - */ } } diff --git a/crates/app/src/views/chat/mod.rs b/crates/app/src/views/chat/mod.rs index 0bda9a9..b44399d 100644 --- a/crates/app/src/views/chat/mod.rs +++ b/crates/app/src/views/chat/mod.rs @@ -1,4 +1,8 @@ -use crate::{get_client, states::chat::room::Room}; +use crate::{ + get_client, + states::chat::room::{Member, Room}, + utils::compare, +}; use gpui::{ div, list, px, AnyElement, AppContext, Context, EventEmitter, Flatten, FocusHandle, FocusableView, IntoElement, ListAlignment, ListState, Model, ParentElement, PathPromptOptions, @@ -33,16 +37,19 @@ pub struct ChatPanel { focus_handle: FocusHandle, // Chat Room id: SharedString, - room: Arc, + owner: PublicKey, + members: Arc<[Member]>, input: View, list: ListState, state: Model, } impl ChatPanel { - pub fn new(room_id: &u64, cx: &mut WindowContext) -> View { - let room = Arc::new(room); - let id = room.id.clone(); + pub fn new(room: Model, cx: &mut WindowContext) -> View { + let room = room.read(cx); + let id = room.id.to_string().into(); + let owner = room.owner; + let members = room.members.clone().into(); let name = room.title.clone().unwrap_or("Untitled".into()); cx.observe_new_views::(|this, cx| { @@ -99,8 +106,9 @@ impl ChatPanel { zoomable: true, focus_handle: cx.focus_handle(), id, + owner, + members, name, - room, input, list, state, @@ -109,11 +117,11 @@ impl ChatPanel { } fn load_messages(&self, cx: &mut ViewContext) { - let members = self.room.members.clone(); - let async_state = self.state.clone(); - let id = self.room.id.to_string(); + let mut all_keys: Vec<_> = self.members.iter().map(|m| m.public_key()).collect(); + all_keys.push(self.owner); - let client = get_client(); + let members = Arc::clone(&self.members); + let async_state = self.state.clone(); let mut async_cx = cx.to_async(); cx.foreground_executor() @@ -121,6 +129,7 @@ impl ChatPanel { let events: anyhow::Result = async_cx .background_executor() .spawn({ + let client = get_client(); let pubkeys = members.iter().map(|m| m.public_key()).collect::>(); async move { @@ -149,14 +158,28 @@ impl ChatPanel { let items: Vec = events .into_iter() .sorted_by_key(|ev| ev.created_at) - .map(|ev| { - let metadata = members - .iter() - .find(|&m| m.public_key() == ev.pubkey) - .unwrap() - .metadata(); + .filter_map(|ev| { + let mut pubkeys: Vec<_> = ev.tags.public_keys().copied().collect(); + pubkeys.push(ev.pubkey); - RoomMessage::new(ev.pubkey, metadata, ev.content, ev.created_at) + if compare(&pubkeys, &all_keys) { + let metadata = if let Some(member) = + members.iter().find(|&m| m.public_key() == ev.pubkey) + { + member.metadata() + } else { + Metadata::default() + }; + + Some(RoomMessage::new( + ev.pubkey, + metadata, + ev.content, + ev.created_at, + )) + } else { + None + } }) .collect(); diff --git a/crates/app/src/views/mod.rs b/crates/app/src/views/mod.rs index 6836672..1a8dfbb 100644 --- a/crates/app/src/views/mod.rs +++ b/crates/app/src/views/mod.rs @@ -1,5 +1,5 @@ mod account; -// mod chat; +mod chat; mod onboarding; mod sidebar; mod welcome; diff --git a/crates/app/src/views/sidebar/inbox.rs b/crates/app/src/views/sidebar/inbox.rs index b725b6d..374425b 100644 --- a/crates/app/src/views/sidebar/inbox.rs +++ b/crates/app/src/views/sidebar/inbox.rs @@ -1,8 +1,12 @@ -use crate::{constants::IMAGE_SERVICE, states::chat::ChatRegistry, utils::ago}; +use crate::{ + constants::IMAGE_SERVICE, + states::chat::ChatRegistry, + utils::ago, + views::app::{AddPanel, PanelKind}, +}; use gpui::{ div, img, percentage, prelude::FluentBuilder, InteractiveElement, IntoElement, ParentElement, - Render, RenderOnce, SharedString, StatefulInteractiveElement, Styled, ViewContext, - WindowContext, + Render, SharedString, StatefulInteractiveElement, Styled, ViewContext, }; use ui::{skeleton::Skeleton, theme::ActiveTheme, v_flex, Collapsible, Icon, IconName, StyledExt}; @@ -19,7 +23,7 @@ impl Inbox { } } - fn skeleton(&self, total: i32) -> impl IntoIterator { + fn render_skeleton(&self, total: i32) -> impl IntoIterator { (0..total).map(|_| { div() .h_8() @@ -31,6 +35,93 @@ impl Inbox { .child(Skeleton::new().w_20().h_3().rounded_sm()) }) } + + fn render_item(&self, cx: &mut ViewContext) -> impl IntoElement { + let weak_model = cx.global::().inbox(); + + if let Some(model) = weak_model.upgrade() { + div().children(model.read(cx).iter().map(|model| { + let room = model.read(cx); + let id = room.id; + let room_id: SharedString = id.to_string().into(); + let ago: SharedString = ago(room.last_seen.as_u64()).into(); + let is_group = room.is_group; + // Get first member + let sender = room.members.first().unwrap(); + // Compute group name based on member' names + let name: SharedString = room + .members + .iter() + .map(|profile| profile.name()) + .collect::>() + .join(", ") + .into(); + + div() + .id(room_id) + .h_8() + .px_1() + .flex() + .items_center() + .justify_between() + .text_xs() + .rounded_md() + .hover(|this| { + this.bg(cx.theme().sidebar_accent) + .text_color(cx.theme().sidebar_accent_foreground) + }) + .child( + div() + .font_medium() + .text_color(cx.theme().sidebar_accent_foreground) + .map(|this| { + if is_group { + this.flex() + .items_center() + .gap_2() + .child(img("brand/avatar.png").size_6().rounded_full()) + .child(name) + } else { + this.flex() + .items_center() + .gap_2() + .child( + img(format!( + "{}/?url={}&w=72&h=72&fit=cover&mask=circle&n=-1", + IMAGE_SERVICE, + sender + .metadata() + .picture + .unwrap_or("brand/avatar.png".into()) + )) + .flex_shrink_0() + .size_6() + .rounded_full(), + ) + .child(sender.name()) + } + }), + ) + .child( + div() + .child(ago) + .text_color(cx.theme().sidebar_accent_foreground.opacity(0.7)), + ) + .on_click(cx.listener(move |this, _, cx| { + this.action(id, cx); + })) + })) + } else { + div().children(self.render_skeleton(5)) + } + } + + fn action(&self, id: u64, cx: &mut ViewContext) { + cx.dispatch_action(Box::new(AddPanel { + panel: PanelKind::Room(id), + position: ui::dock::DockPlacement::Center, + })) + } } impl Collapsible for Inbox { @@ -46,38 +137,6 @@ impl Collapsible for Inbox { impl Render for Inbox { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let mut content = div(); - let weak_model = cx.global::().inbox(); - - if let Some(model) = weak_model.upgrade() { - content = content.children(model.read(cx).iter().map(|model| { - let room = model.read(cx); - let id = room.id.to_string().into(); - let ago = ago(room.last_seen.as_u64()).into(); - // Get first member - let sender = room.members.first().unwrap(); - // Compute group name based on member' names - let name: SharedString = room - .members - .iter() - .map(|profile| profile.name()) - .collect::>() - .join(", ") - .into(); - - InboxListItem::new( - id, - ago, - room.is_group, - name, - sender.metadata().picture, - sender.name(), - ) - })) - } else { - content = content.children(self.skeleton(5)) - } - v_flex() .px_2() .gap_1() @@ -106,100 +165,6 @@ impl Render for Inbox { ) .child(self.label.clone()), ) - .when(!self.is_collapsed, |this| this.child(content)) - } -} - -#[derive(Clone, IntoElement)] -struct InboxListItem { - id: SharedString, - ago: SharedString, - is_group: bool, - group_name: SharedString, - sender_avatar: Option, - sender_name: String, -} - -impl InboxListItem { - pub fn new( - id: SharedString, - ago: SharedString, - is_group: bool, - group_name: SharedString, - sender_avatar: Option, - sender_name: String, - ) -> Self { - Self { - id, - ago, - is_group, - group_name, - sender_avatar, - sender_name, - } - } -} - -impl RenderOnce for InboxListItem { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - let mut content = div() - .font_medium() - .text_color(cx.theme().sidebar_accent_foreground); - - if self.is_group { - content = content - .flex() - .items_center() - .gap_2() - .child(img("brand/avatar.png").size_6().rounded_full()) - .child(self.group_name) - } else { - content = content.flex().items_center().gap_2().map(|mut this| { - // Avatar - if let Some(picture) = self.sender_avatar { - this = this.child( - img(format!( - "{}/?url={}&w=72&h=72&fit=cover&mask=circle&n=-1", - IMAGE_SERVICE, picture - )) - .flex_shrink_0() - .size_6() - .rounded_full(), - ); - } else { - this = this.child( - img("brand/avatar.png") - .flex_shrink_0() - .size_6() - .rounded_full(), - ); - } - - // Display name - this = this.child(self.sender_name); - - this - }) - } - - div() - .id(self.id.clone()) - .h_8() - .px_1() - .flex() - .items_center() - .justify_between() - .text_xs() - .rounded_md() - .hover(|this| { - this.bg(cx.theme().sidebar_accent) - .text_color(cx.theme().sidebar_accent_foreground) - }) - .child(content) - .child( - div() - .child(self.ago) - .text_color(cx.theme().sidebar_accent_foreground.opacity(0.7)), - ) + .when(!self.is_collapsed, |this| this.child(self.render_item(cx))) } }