use crate::{ get_client, states::chat::room::Room, utils::{ago, compare}, }; use gpui::{ div, list, px, AnyElement, AppContext, Context, EventEmitter, Flatten, FocusHandle, FocusableView, IntoElement, ListAlignment, ListState, Model, ParentElement, PathPromptOptions, Pixels, Render, SharedString, Styled, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext, }; use itertools::Itertools; use message::Message; use nostr_sdk::prelude::*; use std::sync::Arc; use ui::{ button::{Button, ButtonVariants}, dock_area::{ panel::{Panel, PanelEvent}, state::PanelState, }, input::{InputEvent, TextInput}, popup_menu::PopupMenu, theme::{scale::ColorScaleStep, ActiveTheme}, v_flex, Icon, IconName, }; mod message; #[derive(Clone)] pub struct State { count: usize, items: Vec, } pub struct ChatPanel { // Panel closeable: bool, zoomable: bool, focus_handle: FocusHandle, // Chat Room id: SharedString, name: SharedString, room: Model, state: Model, list: ListState, input: View, } impl ChatPanel { pub fn new(model: Model, cx: &mut WindowContext) -> View { let room = model.read(cx); let id = room.id.to_string().into(); let name = room.title.clone().unwrap_or("Untitled".into()); cx.observe_new_views::(|this, cx| { this.load_messages(cx); }) .detach(); cx.new_view(|cx| { // Form let input = cx.new_view(|cx| { TextInput::new(cx) .appearance(false) .text_size(ui::Size::Small) .placeholder("Message...") .cleanable() }); let state = cx.new_model(|_| State { count: 0, items: vec![], }); // Send message when user presses enter cx.subscribe( &input, move |this: &mut ChatPanel, view, input_event, cx| { if let InputEvent::PressEnter = input_event { this.send_message(view.downgrade(), cx); } }, ) .detach(); // Update list on every state changes cx.observe(&state, |this, model, cx| { let items = model.read(cx).items.clone(); this.list = ListState::new( items.len(), ListAlignment::Bottom, Pixels(256.), move |idx, _cx| { let item = items.get(idx).unwrap().clone(); div().child(item).into_any_element() }, ); cx.notify(); }) .detach(); cx.observe(&model, |this, model, cx| { this.load_new_messages(model.downgrade(), cx); }) .detach(); Self { closeable: true, zoomable: true, focus_handle: cx.focus_handle(), room: model, list: ListState::new(0, ListAlignment::Bottom, Pixels(256.), move |_, _| { div().into_any_element() }), id, name, input, state, } }) } fn load_messages(&self, cx: &mut ViewContext) { let room = self.room.read(cx); let members = room.members.clone(); let owner = room.owner.clone(); // Get all public keys let all_keys = room.get_all_keys(); // Async let async_state = self.state.clone(); let mut async_cx = cx.to_async(); cx.foreground_executor() .spawn(async move { 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 { let signer = client.signer().await?; let author = signer.get_public_key().await?; let recv = Filter::new() .kind(Kind::PrivateDirectMessage) .author(author) .pubkeys(pubkeys.clone()); let send = Filter::new() .kind(Kind::PrivateDirectMessage) .authors(pubkeys) .pubkey(author); // Get all DM events in database let query = client.database().query(vec![recv, send]).await?; Ok(query) } }) .await; if let Ok(events) = events { let items: Vec = events .into_iter() .sorted_by_key(|ev| ev.created_at) .filter_map(|ev| { let mut pubkeys: Vec<_> = ev.tags.public_keys().copied().collect(); pubkeys.push(ev.pubkey); if compare(&pubkeys, &all_keys) { let member = if let Some(member) = members.iter().find(|&m| m.public_key() == ev.pubkey) { member.to_owned() } else { owner.clone() }; Some(Message::new( member, ev.content.into(), ago(ev.created_at).into(), )) } else { None } }) .collect(); let total = items.len(); _ = async_cx.update_model(&async_state, |a, b| { a.items = items; a.count = total; b.notify(); }); } }) .detach(); } fn load_new_messages(&self, model: WeakModel, cx: &mut ViewContext) { if let Some(model) = model.upgrade() { let room = model.read(cx); let items: Vec = room .new_messages .iter() .filter_map(|event| { room.member(&event.pubkey).map(|member| { Message::new( member, event.content.clone().into(), ago(event.created_at).into(), ) }) }) .collect(); cx.update_model(&self.state, |model, cx| { model.items.extend(items); model.count = model.items.len(); cx.notify(); }); } } fn send_message(&mut self, view: WeakView, cx: &mut ViewContext) { let room = self.room.read(cx); let content = Arc::new(self.input.read(cx).text().to_string()); let owner = room.owner.clone(); let mut members = room.members.to_vec(); members.push(owner.clone()); // Async let async_state = self.state.clone(); let mut async_cx = cx.to_async(); cx.foreground_executor() .spawn(async move { // Send message to all members async_cx .background_executor() .spawn({ let client = get_client(); let content = Arc::clone(&content).to_string(); let tags: Vec = members .iter() .filter_map(|m| { if m.public_key() != owner.public_key() { Some(Tag::public_key(m.public_key())) } else { None } }) .collect(); async move { // Send message to all members for member in members.iter() { _ = client .send_private_msg(member.public_key(), &content, tags.clone()) .await } } }) .detach(); _ = async_cx.update_model(&async_state, |model, cx| { let message = Message::new( owner, content.to_string().into(), ago(Timestamp::now()).into(), ); model.items.extend(vec![message]); model.count = model.items.len(); cx.notify(); }); if let Some(input) = view.upgrade() { _ = async_cx.update_view(&input, |input, cx| { input.set_text("", cx); }); } }) .detach(); } } impl Panel for ChatPanel { fn panel_id(&self) -> SharedString { self.id.clone() } fn panel_metadata(&self) -> Option { None } 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