From 0daebe57628e884dc8556763f0f56ac0c5ad79eb Mon Sep 17 00:00:00 2001 From: reya Date: Fri, 7 Feb 2025 16:11:04 +0700 Subject: [PATCH] feat: add setup inbox relays modal --- Cargo.lock | 119 ++++++------ crates/app/src/main.rs | 42 ++-- crates/app/src/views/app.rs | 87 ++++++++- crates/app/src/views/mod.rs | 1 + crates/app/src/views/onboarding.rs | 9 +- crates/app/src/views/relays.rs | 247 ++++++++++++++++++++++++ crates/app/src/views/sidebar/compose.rs | 11 +- crates/app/src/views/sidebar/mod.rs | 2 +- crates/app/src/views/startup.rs | 12 +- crates/app_state/src/registry.rs | 23 ++- crates/ui/src/modal.rs | 52 ++--- 11 files changed, 453 insertions(+), 152 deletions(-) create mode 100644 crates/app/src/views/relays.rs diff --git a/Cargo.lock b/Cargo.lock index de99e27..52d24af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,7 +219,7 @@ dependencies = [ "serde", "serde_repr", "url", - "zbus 5.3.1", + "zbus 5.4.0", ] [[package]] @@ -1039,10 +1039,10 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "indexmap", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", ] [[package]] @@ -1346,7 +1346,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "proc-macro2", "quote", @@ -2059,7 +2059,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2146,7 +2146,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "proc-macro2", "quote", @@ -2156,7 +2156,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "gpui", "tokio", @@ -2361,7 +2361,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "anyhow", "bytes", @@ -3012,7 +3012,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "anyhow", "bindgen", @@ -3191,7 +3191,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#e57c6e3733de2799294bd70bf325aaf08a60e4d8" +source = "git+https://github.com/rust-nostr/nostr#bbf133bfa3fad539a88fbf29922261c63adf926a" dependencies = [ "aes", "base64", @@ -3219,7 +3219,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#e57c6e3733de2799294bd70bf325aaf08a60e4d8" +source = "git+https://github.com/rust-nostr/nostr#bbf133bfa3fad539a88fbf29922261c63adf926a" dependencies = [ "async-utility", "nostr", @@ -3231,7 +3231,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#e57c6e3733de2799294bd70bf325aaf08a60e4d8" +source = "git+https://github.com/rust-nostr/nostr#bbf133bfa3fad539a88fbf29922261c63adf926a" dependencies = [ "flatbuffers", "nostr", @@ -3241,7 +3241,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#e57c6e3733de2799294bd70bf325aaf08a60e4d8" +source = "git+https://github.com/rust-nostr/nostr#bbf133bfa3fad539a88fbf29922261c63adf926a" dependencies = [ "async-utility", "heed", @@ -3252,7 +3252,7 @@ dependencies = [ [[package]] name = "nostr-relay-pool" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#e57c6e3733de2799294bd70bf325aaf08a60e4d8" +source = "git+https://github.com/rust-nostr/nostr#bbf133bfa3fad539a88fbf29922261c63adf926a" dependencies = [ "async-utility", "async-wsocket", @@ -3268,7 +3268,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#e57c6e3733de2799294bd70bf325aaf08a60e4d8" +source = "git+https://github.com/rust-nostr/nostr#bbf133bfa3fad539a88fbf29922261c63adf926a" dependencies = [ "async-utility", "nostr", @@ -3624,9 +3624,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "oo7" @@ -4051,7 +4051,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "rustls", "socket2", "thiserror 2.0.11", @@ -4069,7 +4069,7 @@ dependencies = [ "getrandom 0.2.15", "rand", "ring", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "rustls", "rustls-pki-types", "slab", @@ -4276,7 +4276,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "derive_refineable", ] @@ -4405,7 +4405,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "anyhow", "bytes", @@ -4511,9 +4511,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -4755,7 +4755,7 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "anyhow", "serde", @@ -5080,7 +5080,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "arrayvec", "log", @@ -5559,9 +5559,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -5588,7 +5588,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.1", + "winnow", ] [[package]] @@ -5902,7 +5902,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#0963401a8d0e7afed461090cb57be8047e1f79c5" +source = "git+https://github.com/zed-industries/zed#d6d0d7d3e4cdb20c678a52df431c38519ed663a0" dependencies = [ "anyhow", "async-fs", @@ -6528,15 +6528,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.1" @@ -6764,9 +6755,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2494e4b3f44d8363eef79a8a75fc0649efb710eef65a66b5e688a5eb4afe678a" +checksum = "cbddd8b6cb25d5d8ec1b23277b45299a98bfb220f1761ca11e186d5c702507f8" dependencies = [ "async-broadcast", "async-executor", @@ -6791,11 +6782,11 @@ dependencies = [ "tracing", "uds_windows", "windows-sys 0.59.0", - "winnow 0.6.26", + "winnow", "xdg-home", - "zbus_macros 5.3.1", - "zbus_names 4.1.1", - "zvariant 5.2.0", + "zbus_macros 5.4.0", + "zbus_names 4.2.0", + "zvariant 5.3.0", ] [[package]] @@ -6813,17 +6804,17 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445efc01929302aee95e2b25bbb62a301ea8a6369466e4278e58e7d1dfb23631" +checksum = "dac404d48b4e9cf193c8b49589f3280ceca5ff63519e7e64f55b4cf9c47ce146" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.98", - "zbus_names 4.1.1", - "zvariant 5.2.0", - "zvariant_utils 3.1.0", + "zbus_names 4.2.0", + "zvariant 5.3.0", + "zvariant_utils 3.2.0", ] [[package]] @@ -6839,14 +6830,14 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.1.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519629a3f80976d89c575895b05677cbc45eaf9f70d62a364d819ba646409cc8" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow 0.6.26", - "zvariant 5.2.0", + "winnow", + "zvariant 5.3.0", ] [[package]] @@ -6978,18 +6969,18 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e6b9b5f1361de2d5e7d9fd1ee5f6f7fcb6060618a1f82f3472f58f2b8d4be9" +checksum = "31c951c21879c6e1d46ac5adfc34f698fefb465d498cf4ac87545849bd71bb5a" dependencies = [ "endi", "enumflags2", "serde", "static_assertions", "url", - "winnow 0.6.26", - "zvariant_derive 5.2.0", - "zvariant_utils 3.1.0", + "winnow", + "zvariant_derive 5.3.0", + "zvariant_utils 3.2.0", ] [[package]] @@ -7007,15 +6998,15 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573a8dd76961957108b10f7a45bac6ab1ea3e9b7fe01aff88325dc57bb8f5c8b" +checksum = "9eeb539471af098d9e63faf428c71ac4cd4efe0b5baa3c8a6b991c5f2543b70e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.98", - "zvariant_utils 3.1.0", + "zvariant_utils 3.2.0", ] [[package]] @@ -7031,14 +7022,14 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd46446ea2a1f353bfda53e35f17633afa79f4fe290a611c94645c69fe96a50" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" dependencies = [ "proc-macro2", "quote", "serde", "static_assertions", "syn 2.0.98", - "winnow 0.6.26", + "winnow", ] diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index eb49bc5..db12c24 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -14,13 +14,13 @@ use gpui::{ }; #[cfg(target_os = "linux")] use gpui::{WindowBackgroundAppearance, WindowDecorations}; -use log::{error, info}; +use log::error; use nostr_sdk::prelude::*; use state::{get_client, initialize_client}; use std::{borrow::Cow, collections::HashSet, ops::Deref, str::FromStr, sync::Arc, time::Duration}; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, oneshot}; use ui::{theme::Theme, Root}; -use views::{app, onboarding, startup::Startup}; +use views::{app, onboarding, startup}; mod asset; mod views; @@ -260,10 +260,7 @@ fn main() { }) .detach(); - let root = cx.new(|cx| { - Root::new(cx.new(|cx| Startup::new(window, cx)).into(), window, cx) - }); - + let root = cx.new(|cx| Root::new(startup::init(window, cx).into(), window, cx)); let weak_root = root.downgrade(); let window_handle = window.window_handle(); let task = cx.read_credentials(KEYRING_SERVICE); @@ -273,7 +270,7 @@ fn main() { cx.spawn(|mut cx| async move { if let Ok(Some((npub, secret))) = task.await { - let (tx, mut rx) = tokio::sync::mpsc::channel::(1); + let (tx, rx) = oneshot::channel::(); cx.background_executor() .spawn(async move { @@ -293,29 +290,18 @@ fn main() { Metadata::new() }; - if tx - .send(NostrProfile::new(public_key, metadata)) - .await - .is_ok() - { - info!("Found account"); - } + _ = tx.send(NostrProfile::new(public_key, metadata)); }) .detach(); - while let Some(profile) = rx.recv().await { + if let Ok(profile) = rx.await { cx.update_window(window_handle, |_, window, cx| { cx.update_global::(|this, cx| { this.set_user(Some(profile.clone())); - - if let Some(root) = this.root() { - cx.update_entity(&root, |this: &mut Root, cx| { - this.set_view( - app::init(profile, window, cx).into(), - cx, - ); - }); - } + this.set_root_view( + app::init(profile, window, cx).into(), + cx, + ); }); }) .unwrap(); @@ -323,11 +309,7 @@ fn main() { } else { cx.update_window(window_handle, |_, window, cx| { cx.update_global::(|this, cx| { - if let Some(root) = this.root() { - cx.update_entity(&root, |this: &mut Root, cx| { - this.set_view(onboarding::init(window, cx).into(), cx); - }); - } + this.set_root_view(onboarding::init(window, cx).into(), cx); }); }) .unwrap(); diff --git a/crates/app/src/views/app.rs b/crates/app/src/views/app.rs index 9de7c37..daa9c80 100644 --- a/crates/app/src/views/app.rs +++ b/crates/app/src/views/app.rs @@ -6,17 +6,20 @@ use gpui::{ Entity, InteractiveElement, IntoElement, ObjectFit, ParentElement, Render, Styled, StyledImage, Window, }; +use nostr_sdk::prelude::*; use serde::Deserialize; use state::get_client; use std::sync::Arc; +use tokio::sync::oneshot; use ui::{ - button::{Button, ButtonVariants}, + button::{Button, ButtonRounded, ButtonVariants}, dock_area::{dock::DockPlacement, DockArea, DockItem}, popup_menu::PopupMenuExt, - Icon, IconName, Root, Sizable, TitleBar, + theme::{scale::ColorScaleStep, ActiveTheme}, + ContextModal, Icon, IconName, Root, Sizable, TitleBar, }; -use super::{chat, contacts, onboarding, profile, settings, sidebar, welcome}; +use super::{chat, contacts, onboarding, profile, relays::Relays, settings, sidebar, welcome}; #[derive(Clone, PartialEq, Eq, Deserialize)] pub enum PanelKind { @@ -79,6 +82,77 @@ impl AppView { view.set_center(center_panel, window, cx); }); + let public_key = account.public_key(); + let window_handle = window.window_handle(); + + // Check user's inbox relays and determine user is ready for NIP17 or not. + // If not, show the setup modal and instruct user setup inbox relays + cx.spawn(|mut cx| async move { + let (tx, rx) = oneshot::channel::(); + + cx.background_spawn(async move { + let client = get_client(); + let filter = Filter::new() + .kind(Kind::InboxRelays) + .author(public_key) + .limit(1); + + let is_ready = if let Ok(events) = client.database().query(filter).await { + events.first_owned().is_some() + } else { + false + }; + + _ = tx.send(is_ready); + }) + .detach(); + + if let Ok(is_ready) = rx.await { + if is_ready { + // + } else { + cx.update_window(window_handle, |_, window, cx| { + let relays = cx.new(|cx| Relays::new(window, cx)); + + window.open_modal(cx, move |this, window, cx| { + let is_loading = relays.read(cx).loading(); + + this.keyboard(false) + .closable(false) + .width(px(420.)) + .title("Your Inbox is not configured") + .child(relays.clone()) + .footer( + div() + .p_2() + .border_t_1() + .border_color( + cx.theme().base.step(cx, ColorScaleStep::FIVE), + ) + .child( + Button::new("update_inbox_relays_btn") + .label("Update") + .primary() + .bold() + .rounded(ButtonRounded::Large) + .w_full() + .loading(is_loading) + .on_click(window.listener_for( + &relays, + |this, _, window, cx| { + this.update(window, cx); + }, + )), + ), + ) + }); + }) + .unwrap(); + } + } + }) + .detach(); + cx.new(|_| Self { account, dock }) } @@ -158,13 +232,8 @@ impl AppView { // Remove user this.set_user(None); - // Update root view - if let Some(root) = this.root() { - cx.update_entity(&root, |this: &mut Root, cx| { - this.set_view(onboarding::init(window, cx).into(), cx); - }); - } + this.set_root_view(onboarding::init(window, cx).into(), cx); }); } } diff --git a/crates/app/src/views/mod.rs b/crates/app/src/views/mod.rs index 54d63c0..9b520a5 100644 --- a/crates/app/src/views/mod.rs +++ b/crates/app/src/views/mod.rs @@ -1,6 +1,7 @@ mod chat; mod contacts; mod profile; +mod relays; mod settings; mod sidebar; mod welcome; diff --git a/crates/app/src/views/onboarding.rs b/crates/app/src/views/onboarding.rs index c4f648f..5a57b2b 100644 --- a/crates/app/src/views/onboarding.rs +++ b/crates/app/src/views/onboarding.rs @@ -13,7 +13,7 @@ use ui::{ input::{InputEvent, TextInput}, notification::NotificationType, theme::{scale::ColorScaleStep, ActiveTheme}, - ContextModal, Root, Size, StyledExt, + ContextModal, Size, StyledExt, }; use super::app; @@ -126,12 +126,7 @@ impl Onboarding { cx.update_window(window_handle, |_, window, cx| { cx.update_global::(|this, cx| { this.set_user(Some(profile.clone())); - - if let Some(root) = this.root() { - cx.update_entity(&root, |this: &mut Root, cx| { - this.set_view(app::init(profile, window, cx).into(), cx); - }); - } + this.set_root_view(app::init(profile, window, cx).into(), cx); }); }) .unwrap(); diff --git a/crates/app/src/views/relays.rs b/crates/app/src/views/relays.rs new file mode 100644 index 0000000..be89acf --- /dev/null +++ b/crates/app/src/views/relays.rs @@ -0,0 +1,247 @@ +use gpui::{ + div, prelude::FluentBuilder, px, uniform_list, AppContext, Context, Entity, FocusHandle, + InteractiveElement, IntoElement, ParentElement, Render, Styled, TextAlign, Window, +}; +use nostr_sdk::prelude::*; +use state::get_client; +use tokio::sync::oneshot; +use ui::{ + button::{Button, ButtonVariants}, + input::{InputEvent, TextInput}, + theme::{scale::ColorScaleStep, ActiveTheme}, + ContextModal, IconName, Sizable, +}; + +pub struct Relays { + relays: Entity>, + input: Entity, + focus_handle: FocusHandle, + is_loading: bool, +} + +impl Relays { + pub fn new(window: &mut Window, cx: &mut Context<'_, Self>) -> Self { + let relays = cx.new(|_| { + let mut list = Vec::with_capacity(10); + + list.push(Url::parse("wss://auth.nostr1.com").unwrap()); + list.push(Url::parse("wss://relay.0xchat.com").unwrap()); + + list + }); + + let input = cx.new(|cx| { + TextInput::new(window, cx) + .text_size(ui::Size::XSmall) + .small() + .placeholder("wss://...") + }); + + cx.subscribe_in(&input, window, move |this, _, input_event, window, cx| { + if let InputEvent::PressEnter = input_event { + this.add(window, cx); + } + }) + .detach(); + + Self { + relays, + input, + is_loading: false, + focus_handle: cx.focus_handle(), + } + } + + pub fn update(&mut self, window: &mut Window, cx: &mut Context) { + let relays = self.relays.read(cx).clone(); + let window_handle = window.window_handle(); + + self.set_loading(true, cx); + + cx.spawn(|this, mut cx| async move { + let (tx, rx) = oneshot::channel(); + + cx.background_spawn(async move { + let client = get_client(); + let signer = client.signer().await.unwrap(); + let public_key = signer.get_public_key().await.unwrap(); + + let tags: Vec = relays + .into_iter() + .map(|relay| Tag::custom(TagKind::Relay, vec![relay.to_string()])) + .collect(); + + let event = EventBuilder::new(Kind::InboxRelays, "") + .tags(tags) + .build(public_key) + .sign(&signer) + .await + .unwrap(); + + if let Ok(output) = client.send_event(&event).await { + _ = tx.send(output.val); + }; + }) + .detach(); + + if rx.await.is_ok() { + cx.update_window(window_handle, |_, window, cx| { + window.close_modal(cx); + this.update(cx, |this, cx| { + this.set_loading(false, cx); + }) + .unwrap(); + }) + .unwrap(); + } + }) + .detach(); + } + + pub fn loading(&self) -> bool { + self.is_loading + } + + fn set_loading(&mut self, status: bool, cx: &mut Context) { + self.is_loading = status; + cx.notify(); + } + + fn add(&mut self, window: &mut Window, cx: &mut Context) { + let value = self.input.read(cx).text().to_string(); + + if !value.starts_with("ws") { + return; + } + + if let Ok(url) = Url::parse(&value) { + self.relays.update(cx, |this, cx| { + if !this.contains(&url) { + this.push(url); + cx.notify(); + } + }); + + self.input.update(cx, |this, cx| { + this.set_text("", window, cx); + }); + } + } + + fn remove(&mut self, ix: usize, _window: &mut Window, cx: &mut Context) { + self.relays.update(cx, |this, cx| { + this.remove(ix); + cx.notify(); + }); + } +} + +impl Render for Relays { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let msg = "In order to receive messages from others, you need to setup Inbox Relays. You can use the recommend relays or add more."; + + div() + .track_focus(&self.focus_handle) + .flex() + .flex_col() + .gap_2() + .child( + div() + .px_2() + .text_xs() + .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) + .child(msg), + ) + .child( + div() + .px_2() + .flex() + .flex_col() + .gap_2() + .child( + div() + .flex() + .items_center() + .gap_2() + .child(self.input.clone()) + .child( + Button::new("add_relay_btn") + .icon(IconName::Plus) + .small() + .rounded(px(cx.theme().radius)) + .on_click( + cx.listener(|this, _, window, cx| this.add(window, cx)), + ), + ), + ) + .map(|this| { + let view = cx.entity(); + let relays = self.relays.read(cx).clone(); + let total = relays.len(); + + if !relays.is_empty() { + this.child( + uniform_list( + view, + "relays", + total, + move |_, range, _window, cx| { + let mut items = Vec::with_capacity(total); + + for ix in range { + let item = relays.get(ix).unwrap().clone().to_string(); + + items.push( + div().group("").w_full().h_9().py_0p5().child( + div() + .px_2() + .h_full() + .w_full() + .flex() + .items_center() + .justify_between() + .rounded(px(cx.theme().radius)) + .bg(cx + .theme() + .base + .step(cx, ColorScaleStep::THREE)) + .text_xs() + .child(item) + .child( + Button::new("remove_{ix}") + .icon(IconName::Close) + .xsmall() + .ghost() + .invisible() + .group_hover("", |this| { + this.visible() + }) + .on_click(cx.listener( + move |this, _, window, cx| { + this.remove(ix, window, cx) + }, + )), + ), + ), + ) + } + + items + }, + ) + .min_h(px(120.)), + ) + } else { + this.h_20() + .mb_2() + .flex() + .items_center() + .justify_center() + .text_xs() + .text_align(TextAlign::Center) + .child("Please add some relays.") + } + }), + ) + } +} diff --git a/crates/app/src/views/sidebar/compose.rs b/crates/app/src/views/sidebar/compose.rs index ae70956..1a68bf0 100644 --- a/crates/app/src/views/sidebar/compose.rs +++ b/crates/app/src/views/sidebar/compose.rs @@ -306,7 +306,7 @@ impl Render for Compose { .gap_2() .px_2() .child( - Button::new("add") + Button::new("add_user_to_compose_btn") .icon(IconName::Plus) .small() .rounded(ButtonRounded::Size(px(9999.))) @@ -319,7 +319,10 @@ impl Render for Compose { ) .map(|this| { if let Some(contacts) = self.contacts.read(cx).clone() { - if contacts.is_empty() { + let view = cx.entity(); + let total = contacts.len(); + + if total != 0 { this.child( div() .w_full() @@ -350,9 +353,9 @@ impl Render for Compose { } else { this.child( uniform_list( - cx.entity().clone(), + view, "contacts", - contacts.len(), + total, move |this, range, _window, cx| { let selected = this.selected.read(cx); let mut items = Vec::new(); diff --git a/crates/app/src/views/sidebar/mod.rs b/crates/app/src/views/sidebar/mod.rs index 0f6b761..f4e7e72 100644 --- a/crates/app/src/views/sidebar/mod.rs +++ b/crates/app/src/views/sidebar/mod.rs @@ -64,7 +64,7 @@ impl Sidebar { .border_t_1() .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE)) .child( - Button::new("create") + Button::new("create_dm_btn") .label(label) .primary() .bold() diff --git a/crates/app/src/views/startup.rs b/crates/app/src/views/startup.rs index b70e12d..b36360e 100644 --- a/crates/app/src/views/startup.rs +++ b/crates/app/src/views/startup.rs @@ -1,11 +1,17 @@ -use gpui::{div, svg, Context, IntoElement, ParentElement, Render, Styled, Window}; +use gpui::{ + div, svg, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, Styled, Window, +}; use ui::theme::{scale::ColorScaleStep, ActiveTheme}; +pub fn init(window: &mut Window, cx: &mut App) -> Entity { + Startup::new(window, cx) +} + pub struct Startup {} impl Startup { - pub fn new(_window: &mut Window, _cx: &mut Context<'_, Self>) -> Self { - Self {} + pub fn new(_window: &mut Window, cx: &mut App) -> Entity { + cx.new(|_| Self {}) } } diff --git a/crates/app_state/src/registry.rs b/crates/app_state/src/registry.rs index 604e140..42beb6e 100644 --- a/crates/app_state/src/registry.rs +++ b/crates/app_state/src/registry.rs @@ -2,7 +2,7 @@ use common::{ constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID}, profile::NostrProfile, }; -use gpui::{App, Entity, Global, WeakEntity}; +use gpui::{AnyView, App, Global, WeakEntity}; use nostr_sdk::prelude::*; use state::get_client; use std::time::Duration; @@ -38,11 +38,6 @@ impl AppRegistry { // 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().exit_policy( - ReqExitPolicy::WaitDurationAfterEOSE(Duration::from_secs(5)), - ); - // Create a filter for getting new message let new_message = Filter::new() .kind(Kind::GiftWrap) @@ -51,7 +46,13 @@ impl AppRegistry { // Subscribe for all messages _ = client - .subscribe_with_id(all_messages_sub_id, all_messages, Some(opts)) + .subscribe_with_id( + all_messages_sub_id, + all_messages, + Some(SubscribeAutoCloseOptions::default().exit_policy( + ReqExitPolicy::WaitDurationAfterEOSE(Duration::from_secs(5)), + )), + ) .await; // Subscribe for new message @@ -75,7 +76,11 @@ impl AppRegistry { self.user.clone() } - pub fn root(&self) -> Option> { - self.root.upgrade() + pub fn set_root_view(&self, view: AnyView, cx: &mut App) { + if let Err(e) = self.root.update(cx, |this, cx| { + this.set_view(view, cx); + }) { + println!("Error: {}", e) + } } } diff --git a/crates/ui/src/modal.rs b/crates/ui/src/modal.rs index dc0d978..f84cb43 100644 --- a/crates/ui/src/modal.rs +++ b/crates/ui/src/modal.rs @@ -32,7 +32,7 @@ pub struct Modal { max_width: Option, margin_top: Option, on_close: OnClose, - show_close: bool, + closable: bool, keyboard: bool, /// This will be change when open the modal, the focus handle is create when open the modal. pub(crate) focus_handle: FocusHandle, @@ -61,9 +61,9 @@ impl Modal { max_width: None, overlay: true, keyboard: true, + closable: true, layer_ix: 0, on_close: Rc::new(|_, _, _| {}), - show_close: true, } } @@ -88,9 +88,9 @@ impl Modal { self } - /// Sets the false to hide close icon, default: true - pub fn show_close(mut self, show_close: bool) -> Self { - self.show_close = show_close; + /// Sets the false to make modal unclosable, default: true + pub fn closable(mut self, closable: bool) -> Self { + self.closable = closable; self } @@ -170,31 +170,20 @@ impl RenderOnce for Modal { .when(self.overlay, |this| { this.bg(cx.theme().base.step_alpha(cx, ColorScaleStep::EIGHT)) }) - .on_mouse_down(MouseButton::Left, { - let on_close = self.on_close.clone(); - move |_, window, cx| { - on_close(&ClickEvent::default(), window, cx); - window.close_modal(cx); - } + .when(self.closable, |this| { + this.on_mouse_down(MouseButton::Left, { + let on_close = self.on_close.clone(); + move |_, window, cx| { + on_close(&ClickEvent::default(), window, cx); + window.close_modal(cx); + } + }) }) .child( self.base .id(SharedString::from(format!("modal-{layer_ix}"))) .key_context(CONTEXT) .track_focus(&self.focus_handle) - .when(self.keyboard, |this| { - this.on_action({ - let on_close = self.on_close.clone(); - move |_: &Escape, window, cx| { - // FIXME: - // - // Here some Modal have no focus_handle, so it will not work will Escape key. - // But by now, we `cx.close_modal()` going to close the last active model, so the Escape is unexpected to work. - on_close(&ClickEvent::default(), window, cx); - window.close_modal(cx); - } - }) - }) .absolute() .occlude() .relative() @@ -215,7 +204,20 @@ impl RenderOnce for Modal { .child(title), ) }) - .when(self.show_close, |this| { + .when(self.keyboard, |this| { + this.on_action({ + let on_close = self.on_close.clone(); + move |_: &Escape, window, cx| { + // FIXME: + // + // Here some Modal have no focus_handle, so it will not work will Escape key. + // But by now, we `cx.close_modal()` going to close the last active model, so the Escape is unexpected to work. + on_close(&ClickEvent::default(), window, cx); + window.close_modal(cx); + } + }) + }) + .when(self.closable, |this| { this.child( Button::new(SharedString::from(format!( "modal-close-{layer_ix}"