diff --git a/assets/brand/avatar.png b/assets/brand/avatar.png
new file mode 100644
index 0000000..af45456
Binary files /dev/null and b/assets/brand/avatar.png differ
diff --git a/assets/icons/chevron-down.svg b/assets/icons/chevron-down.svg
new file mode 100644
index 0000000..944e76e
--- /dev/null
+++ b/assets/icons/chevron-down.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/ellipsis.svg b/assets/icons/ellipsis.svg
index 05eec51..f3113af 100644
--- a/assets/icons/ellipsis.svg
+++ b/assets/icons/ellipsis.svg
@@ -1,4 +1,4 @@
diff --git a/assets/icons/minimize.svg b/assets/icons/minimize.svg
index 825fc19..67009bf 100644
--- a/assets/icons/minimize.svg
+++ b/assets/icons/minimize.svg
@@ -1,3 +1,3 @@
diff --git a/assets/icons/panel-left-open.svg b/assets/icons/panel-left-open.svg
index a4914be..6f79cd1 100644
--- a/assets/icons/panel-left-open.svg
+++ b/assets/icons/panel-left-open.svg
@@ -1,4 +1,4 @@
diff --git a/assets/icons/panel-left.svg b/assets/icons/panel-left.svg
index a4914be..6f79cd1 100644
--- a/assets/icons/panel-left.svg
+++ b/assets/icons/panel-left.svg
@@ -1,4 +1,4 @@
diff --git a/crates/app/src/constants.rs b/crates/app/src/constants.rs
index 625a0e5..1574c22 100644
--- a/crates/app/src/constants.rs
+++ b/crates/app/src/constants.rs
@@ -1,5 +1,6 @@
pub const KEYRING_SERVICE: &str = "Coop Safe Storage";
pub const APP_NAME: &str = "coop";
pub const FAKE_SIG: &str = "f9e79d141c004977192d05a86f81ec7c585179c371f7350a5412d33575a2a356433f58e405c2296ed273e2fe0aafa25b641e39cc4e1f3f261ebf55bce0cbac83";
-pub const SUBSCRIPTION_ID: &str = "listen_new_giftwrap";
+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;
diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs
index fb71539..eb0ea20 100644
--- a/crates/app/src/main.rs
+++ b/crates/app/src/main.rs
@@ -9,9 +9,10 @@ use std::{
sync::{Arc, OnceLock},
time::Duration,
};
+use tokio::sync::mpsc;
-use constants::{APP_NAME, FAKE_SIG};
-use states::account::AccountState;
+use constants::{ALL_MESSAGES_SUB_ID, APP_NAME, FAKE_SIG, NEW_MESSAGE_SUB_ID};
+use states::{account::AccountRegistry, chat::ChatRegistry, signal::SignalRegistry};
use views::app::AppView;
pub mod asset;
@@ -69,11 +70,76 @@ async fn main() {
// Connect to all relays
_ = client.connect().await;
+ // Channel for metadata signal
+ let (signal_tx, mut signal_rx) = mpsc::channel::(1000); // TODO: adjust?
+
+ // Channel for new chat
+ let (new_chat_tx, mut new_chat_rx) = mpsc::channel::(1000); // TODO: adjust?
+
+ // Channel for all chats
+ let (all_chats_tx, mut all_chats_rx) = mpsc::channel::(1);
+
+ tokio::spawn(async move {
+ let sig = Signature::from_str(FAKE_SIG).unwrap();
+ let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
+ let new_message_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
+
+ while let Ok(notification) = notifications.recv().await {
+ #[allow(clippy::collapsible_match)]
+ if let RelayPoolNotification::Message { message, .. } = notification {
+ if let RelayMessage::Event {
+ event,
+ subscription_id,
+ } = message
+ {
+ if event.kind == Kind::GiftWrap {
+ if let Ok(UnwrappedGift { rumor, .. }) =
+ client.unwrap_gift_wrap(&event).await
+ {
+ let mut rumor_clone = rumor.clone();
+
+ // Compute event id if not exist
+ rumor_clone.ensure_id();
+
+ if let Some(id) = rumor_clone.id {
+ let ev = Event::new(
+ id,
+ rumor_clone.pubkey,
+ rumor_clone.created_at,
+ rumor_clone.kind,
+ rumor_clone.tags,
+ rumor_clone.content,
+ sig,
+ );
+
+ // Save rumor to database to further query
+ _ = client.database().save_event(&ev).await;
+
+ // Send event to channel
+ if subscription_id == new_message_sub_id {
+ _ = new_chat_tx.send(ev).await;
+ }
+ }
+ }
+ } else if event.kind == Kind::Metadata {
+ _ = signal_tx.send(event.pubkey).await;
+ }
+ } else if let RelayMessage::EndOfStoredEvents(subscription_id) = message {
+ if all_messages_sub_id == subscription_id {
+ _ = all_chats_tx.send(1).await;
+ }
+ }
+ }
+ }
+ });
+
App::new()
.with_assets(Assets)
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new()))
.run(move |cx| {
- AccountState::set_global(cx);
+ AccountRegistry::set_global(cx);
+ ChatRegistry::set_global(cx);
+ SignalRegistry::set_global(cx);
// Initialize components
coop_ui::init(cx);
@@ -81,46 +147,32 @@ async fn main() {
// Set quit action
cx.on_action(quit);
- // Handle notifications
- cx.background_executor()
- .spawn({
- let sig = Signature::from_str(FAKE_SIG).unwrap();
- async move {
- while let Ok(notification) = notifications.recv().await {
- #[allow(clippy::collapsible_match)]
- if let RelayPoolNotification::Message { message, .. } = notification {
- if let RelayMessage::Event { event, .. } = message {
- if event.kind == Kind::GiftWrap {
- if let Ok(UnwrappedGift { rumor, .. }) =
- client.unwrap_gift_wrap(&event).await
- {
- let mut rumor_clone = rumor.clone();
+ cx.spawn(|async_cx| async move {
+ while let Some(public_key) = signal_rx.recv().await {
+ _ = async_cx.update_global::(|state, _cx| {
+ state.push(public_key);
+ });
+ }
+ })
+ .detach();
- // Compute event id if not exist
- rumor_clone.ensure_id();
+ cx.spawn(|async_cx| async move {
+ while let Some(event) = new_chat_rx.recv().await {
+ _ = async_cx.update_global::(|state, cx| {
+ state.push(event, cx);
+ });
+ }
+ })
+ .detach();
- if let Some(id) = rumor_clone.id {
- let ev = Event::new(
- id,
- rumor_clone.pubkey,
- rumor_clone.created_at,
- rumor_clone.kind,
- rumor_clone.tags,
- rumor_clone.content,
- sig,
- );
-
- // Save rumor to database to further query
- _ = client.database().save_event(&ev).await
- }
- }
- }
- }
- }
- }
- }
- })
- .detach();
+ cx.spawn(|async_cx| async move {
+ while let Some(_n) = all_chats_rx.recv().await {
+ _ = async_cx.update_global::(|state, cx| {
+ state.load(cx);
+ });
+ }
+ })
+ .detach();
// Set window size
let bounds = Bounds::centered(None, size(px(900.0), px(680.0)), cx);
diff --git a/crates/app/src/states/account.rs b/crates/app/src/states/account.rs
index 2982355..0419e9c 100644
--- a/crates/app/src/states/account.rs
+++ b/crates/app/src/states/account.rs
@@ -3,33 +3,36 @@ use gpui::*;
use nostr_sdk::prelude::*;
use std::time::Duration;
-use crate::{constants::SUBSCRIPTION_ID, get_client};
+use crate::{
+ constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID},
+ get_client,
+};
-pub struct AccountState {
- pub in_use: Option,
+pub struct AccountRegistry {
+ public_key: Option,
}
-impl Global for AccountState {}
+impl Global for AccountRegistry {}
-impl AccountState {
+impl AccountRegistry {
pub fn set_global(cx: &mut AppContext) {
cx.set_global(Self::new());
cx.observe_global::(|cx| {
let state = cx.global::();
- if let Some(public_key) = state.in_use {
+ if let Some(public_key) = state.public_key {
let client = get_client();
+ let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
+ let new_message_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
+
// Create a filter for getting all gift wrapped events send to current user
let all_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
// Subscription options
- let opts = SubscribeAutoCloseOptions::default().filter(
- FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(10)),
- );
-
- let subscription_id = SubscriptionId::new(SUBSCRIPTION_ID);
+ let opts = SubscribeAutoCloseOptions::default()
+ .filter(FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(5)));
// Create a filter for getting new message
let new_message = Filter::new()
@@ -38,14 +41,15 @@ impl AccountState {
.limit(0);
spawn(async move {
+ // Subscribe for all messages
if client
- .subscribe(vec![all_messages], Some(opts))
+ .subscribe_with_id(all_messages_sub_id, vec![all_messages], Some(opts))
.await
.is_ok()
{
// Subscribe for new message
_ = client
- .subscribe_with_id(subscription_id, vec![new_message], None)
+ .subscribe_with_id(new_message_sub_id, vec![new_message], None)
.await
}
});
@@ -54,7 +58,15 @@ impl AccountState {
.detach();
}
+ pub fn set_user(&mut self, public_key: Option) {
+ self.public_key = public_key
+ }
+
+ pub fn is_user_logged_in(&self) -> bool {
+ self.public_key.is_some()
+ }
+
fn new() -> Self {
- Self { in_use: None }
+ Self { public_key: None }
}
}
diff --git a/crates/app/src/states/chat.rs b/crates/app/src/states/chat.rs
new file mode 100644
index 0000000..8b02f35
--- /dev/null
+++ b/crates/app/src/states/chat.rs
@@ -0,0 +1,75 @@
+use gpui::*;
+use itertools::Itertools;
+use nostr_sdk::prelude::*;
+use std::cmp::Reverse;
+
+use crate::get_client;
+
+pub struct ChatRegistry {
+ events: Model