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, Pixels, Render, SharedString, Styled, View, ViewContext, VisualContext, WindowContext, }; use itertools::Itertools; use message::RoomMessage; use nostr_sdk::prelude::*; use std::sync::Arc; use ui::{ button::{Button, ButtonVariants}, dock::{Panel, PanelEvent, PanelState}, input::{InputEvent, TextInput}, popup_menu::PopupMenu, theme::ActiveTheme, v_flex, Icon, IconName, }; mod message; #[derive(Clone)] pub struct State { count: usize, items: Vec, } pub struct ChatPanel { // Panel name: SharedString, closeable: bool, zoomable: bool, focus_handle: FocusHandle, // Chat Room id: SharedString, owner: PublicKey, members: Arc<[Member]>, input: View, list: ListState, state: Model, } impl ChatPanel { 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| { 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() }); // Send message when user presses enter on form. cx.subscribe(&input, move |this: &mut ChatPanel, _, input_event, cx| { if let InputEvent::PressEnter = input_event { this.send_message(cx); } }) .detach(); let state = cx.new_model(|_| State { count: 0, items: vec![], }); 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(); let list = ListState::new(0, ListAlignment::Bottom, Pixels(256.), move |_, _| { div().into_any_element() }); Self { closeable: true, zoomable: true, focus_handle: cx.focus_handle(), id, owner, members, name, input, list, state, } }) } fn load_messages(&self, cx: &mut ViewContext) { let mut all_keys: Vec<_> = self.members.iter().map(|m| m.public_key()).collect(); all_keys.push(self.owner); let members = Arc::clone(&self.members); 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 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(); let total = items.len(); _ = async_cx.update_model(&async_state, |a, b| { a.items = items; a.count = total; b.notify(); }); } }) .detach(); } fn send_message(&mut self, cx: &mut ViewContext) {} } 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