@@ -40,7 +40,7 @@ use ui::dock_area::{ClosePanel, DockArea, DockItem};
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::notification::Notification;
|
||||
use ui::popup_menu::PopupMenuExt;
|
||||
use ui::{h_flex, v_flex, ContextModal, IconName, Root, Sizable, StyledExt};
|
||||
use ui::{h_flex, v_flex, ContextModal, Disableable, IconName, Root, Sizable, StyledExt};
|
||||
|
||||
use crate::actions::{DarkMode, Logout, Settings};
|
||||
use crate::views::compose::compose_button;
|
||||
@@ -64,10 +64,17 @@ pub fn new_account(window: &mut Window, cx: &mut App) {
|
||||
}
|
||||
|
||||
pub struct ChatSpace {
|
||||
// Workspace
|
||||
title_bar: Entity<TitleBar>,
|
||||
dock: Entity<DockArea>,
|
||||
auth_requests: Vec<(String, RelayUrl)>,
|
||||
|
||||
// Temporarily store all authentication requests
|
||||
auth_requests: HashMap<AuthRequest, bool>,
|
||||
|
||||
// Local state to determine if the user has set up NIP-17 relays
|
||||
has_nip17_relays: bool,
|
||||
|
||||
// System
|
||||
_subscriptions: SmallVec<[Subscription; 3]>,
|
||||
_tasks: SmallVec<[Task<()>; 5]>,
|
||||
}
|
||||
@@ -174,7 +181,7 @@ impl ChatSpace {
|
||||
Self {
|
||||
dock,
|
||||
title_bar,
|
||||
auth_requests: vec![],
|
||||
auth_requests: HashMap::new(),
|
||||
has_nip17_relays: true,
|
||||
_subscriptions: subscriptions,
|
||||
_tasks: tasks,
|
||||
@@ -279,9 +286,10 @@ impl ChatSpace {
|
||||
|
||||
loop {
|
||||
if client.has_signer().await {
|
||||
total_loops += 1;
|
||||
|
||||
if css.gift_wrap_processing.load(Ordering::Acquire) {
|
||||
is_start_processing = true;
|
||||
total_loops += 1;
|
||||
|
||||
// Reset gift wrap processing flag
|
||||
let _ = css.gift_wrap_processing.compare_exchange(
|
||||
@@ -295,8 +303,8 @@ impl ChatSpace {
|
||||
ingester.send(signal).await;
|
||||
} else {
|
||||
// Only run further if we are already processing
|
||||
// Wait until after 3 loops to prevent exiting early while events are still being processed
|
||||
if is_start_processing && total_loops >= 3 {
|
||||
// Wait until after 2 loops to prevent exiting early while events are still being processed
|
||||
if is_start_processing && total_loops >= 2 {
|
||||
let signal = Signal::GiftWrapProcess(UnwrappingStatus::Complete);
|
||||
ingester.send(signal).await;
|
||||
|
||||
@@ -522,21 +530,19 @@ impl ChatSpace {
|
||||
});
|
||||
}
|
||||
Signal::Auth(req) => {
|
||||
let relay_url = &req.url;
|
||||
let challenge = &req.challenge;
|
||||
let url = &req.url;
|
||||
let auto_auth = AppSettings::get_auto_auth(cx);
|
||||
let is_authenticated_relays =
|
||||
AppSettings::read_global(cx).is_authenticated_relays(relay_url);
|
||||
let is_authenticated = AppSettings::read_global(cx).is_authenticated(url);
|
||||
|
||||
view.update(cx, |this, cx| {
|
||||
this.push_auth_request(challenge, relay_url, cx);
|
||||
this.push_auth_request(&req, cx);
|
||||
|
||||
if auto_auth && is_authenticated_relays {
|
||||
if auto_auth && is_authenticated {
|
||||
// Automatically authenticate if the relay is authenticated before
|
||||
this.auth(challenge, relay_url, window, cx);
|
||||
this.auth(req, window, cx);
|
||||
} else {
|
||||
// Otherwise open the auth request popup
|
||||
this.open_auth_request(challenge, relay_url, window, cx);
|
||||
this.open_auth_request(req, window, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
@@ -780,19 +786,18 @@ impl ChatSpace {
|
||||
};
|
||||
}
|
||||
|
||||
fn auth(
|
||||
&mut self,
|
||||
challenge: &str,
|
||||
url: &RelayUrl,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
fn auth(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let settings = AppSettings::global(cx);
|
||||
let challenge = challenge.to_string();
|
||||
let url = url.to_owned();
|
||||
|
||||
let challenge = req.challenge.to_owned();
|
||||
let url = req.url.to_owned();
|
||||
|
||||
let challenge_clone = challenge.clone();
|
||||
let url_clone = url.clone();
|
||||
|
||||
// Set Coop is sending auth for this request
|
||||
self.sending_auth_request(&challenge, cx);
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let css = css();
|
||||
@@ -885,7 +890,7 @@ impl ChatSpace {
|
||||
.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
this.update_in(cx, |_, window, cx| {
|
||||
window.push_notification(Notification::error(e.to_string()), cx);
|
||||
})
|
||||
.ok();
|
||||
@@ -895,16 +900,10 @@ impl ChatSpace {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn open_auth_request(
|
||||
&mut self,
|
||||
challenge: &str,
|
||||
relay_url: &RelayUrl,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
fn open_auth_request(&mut self, req: AuthRequest, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let weak_view = cx.entity().downgrade();
|
||||
let challenge = challenge.to_string();
|
||||
let relay_url = relay_url.to_owned();
|
||||
let challenge = req.challenge.to_owned();
|
||||
let relay_url = req.url.to_owned();
|
||||
let url_as_string = SharedString::from(relay_url.to_string());
|
||||
|
||||
let note = Notification::new()
|
||||
@@ -929,19 +928,25 @@ impl ChatSpace {
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.action(move |_window, _cx| {
|
||||
.action(move |_window, cx| {
|
||||
let weak_view = weak_view.clone();
|
||||
let challenge = challenge.clone();
|
||||
let relay_url = relay_url.clone();
|
||||
let req = req.clone();
|
||||
let loading = weak_view
|
||||
.read_with(cx, |this, cx| {
|
||||
this.is_sending_auth_request(&req.challenge, cx)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Button::new("approve")
|
||||
.label(t!("common.approve"))
|
||||
.small()
|
||||
.primary()
|
||||
.loading(loading)
|
||||
.disabled(loading)
|
||||
.on_click(move |_e, window, cx| {
|
||||
weak_view
|
||||
.update(cx, |this, cx| {
|
||||
this.auth(&challenge, &relay_url, window, cx);
|
||||
this.auth(req.clone(), window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -951,23 +956,42 @@ impl ChatSpace {
|
||||
}
|
||||
|
||||
fn reopen_auth_request(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
for (challenge, relay_url) in self.auth_requests.clone().iter() {
|
||||
self.open_auth_request(challenge, relay_url, window, cx);
|
||||
for req in self.auth_requests.clone().into_iter() {
|
||||
self.open_auth_request(req.0, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_auth_request(&mut self, challenge: &str, url: &RelayUrl, cx: &mut Context<Self>) {
|
||||
self.auth_requests.push((challenge.into(), url.to_owned()));
|
||||
fn push_auth_request(&mut self, req: &AuthRequest, cx: &mut Context<Self>) {
|
||||
self.auth_requests.insert(req.to_owned(), false);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
|
||||
if let Some(ix) = self.auth_requests.iter().position(|(c, _)| c == challenge) {
|
||||
self.auth_requests.remove(ix);
|
||||
cx.notify();
|
||||
fn sending_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
|
||||
for (req, status) in self.auth_requests.iter_mut() {
|
||||
if req.challenge == challenge {
|
||||
*status = true;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_sending_auth_request(&self, challenge: &str, _cx: &App) -> bool {
|
||||
if let Some(req) = self
|
||||
.auth_requests
|
||||
.iter()
|
||||
.find(|(req, _)| req.challenge == challenge)
|
||||
{
|
||||
req.1.to_owned()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_auth_request(&mut self, challenge: &str, cx: &mut Context<Self>) {
|
||||
self.auth_requests.retain(|r, _| r.challenge != challenge);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_onboarding_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let panel = Arc::new(onboarding::init(window, cx));
|
||||
let center = DockItem::panel(panel);
|
||||
@@ -1016,6 +1040,11 @@ impl ChatSpace {
|
||||
});
|
||||
}
|
||||
|
||||
fn set_no_nip17_relays(&mut self, cx: &mut Context<Self>) {
|
||||
self.has_nip17_relays = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn load_local_account(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let task = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
@@ -1059,11 +1088,6 @@ impl ChatSpace {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn set_no_nip17_relays(&mut self, cx: &mut Context<Self>) {
|
||||
self.has_nip17_relays = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_settings(&mut self, _ev: &Settings, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let view = preferences::init(window, cx);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use gpui::{
|
||||
use i18n::{shared_t, t};
|
||||
use nostr_connect::prelude::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use theme::ActiveTheme;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
@@ -44,6 +45,7 @@ pub struct Account {
|
||||
// Panel
|
||||
name: SharedString,
|
||||
focus_handle: FocusHandle,
|
||||
_tasks: SmallVec<[Task<()>; 1]>,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
@@ -59,6 +61,7 @@ impl Account {
|
||||
loading: false,
|
||||
name: "Account".into(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
_tasks: smallvec![],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,24 +92,25 @@ impl Account {
|
||||
// Handle auth url with the default browser
|
||||
signer.auth_url_handler(CoopAuthUrlHandler);
|
||||
|
||||
// Handle connection
|
||||
cx.spawn_in(window, async move |_this, cx| {
|
||||
let client = nostr_client();
|
||||
self._tasks.push(
|
||||
// Handle connection
|
||||
cx.spawn_in(window, async move |_this, cx| {
|
||||
let client = nostr_client();
|
||||
|
||||
match signer.bunker_uri().await {
|
||||
Ok(_) => {
|
||||
// Set the client's signer with the current nostr connect instance
|
||||
client.set_signer(signer).await;
|
||||
match signer.bunker_uri().await {
|
||||
Ok(_) => {
|
||||
// Set the client's signer with the current nostr connect instance
|
||||
client.set_signer(signer).await;
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(e.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(e.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -240,24 +244,26 @@ impl Account {
|
||||
}
|
||||
|
||||
fn logout(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let ingester = ingester();
|
||||
self._tasks.push(
|
||||
// Reset the nostr client in the background
|
||||
cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let ingester = ingester();
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::ApplicationSpecificData)
|
||||
.identifier(ACCOUNT_IDENTIFIER);
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::ApplicationSpecificData)
|
||||
.identifier(ACCOUNT_IDENTIFIER);
|
||||
|
||||
// Delete account
|
||||
client.database().delete(filter).await.ok();
|
||||
// Delete account
|
||||
client.database().delete(filter).await.ok();
|
||||
|
||||
// Unset the client's signer
|
||||
client.unset_signer().await;
|
||||
// Unset the client's signer
|
||||
client.unset_signer().await;
|
||||
|
||||
// Notify the channel about the signer being unset
|
||||
ingester.send(Signal::SignerUnset).await;
|
||||
})
|
||||
.detach();
|
||||
// Notify the channel about the signer being unset
|
||||
ingester.send(Signal::SignerUnset).await;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||
|
||||
@@ -168,26 +168,28 @@ impl Chat {
|
||||
}
|
||||
|
||||
/// Load all messages belonging to this room
|
||||
fn load_messages(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn load_messages(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let load_messages = self.room.read(cx).load_messages(cx);
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
match load_messages.await {
|
||||
Ok(events) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.insert_messages(events, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(e.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
self._tasks.push(
|
||||
// Run the task in the background
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
match load_messages.await {
|
||||
Ok(events) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.insert_messages(events, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(e.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, img, px, relative, svg, AnyElement, App, AppContext, ClipboardItem, Context, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, Image, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window,
|
||||
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Window,
|
||||
};
|
||||
use i18n::{shared_t, t};
|
||||
use nostr_connect::prelude::*;
|
||||
@@ -66,8 +66,8 @@ pub struct Onboarding {
|
||||
// Panel
|
||||
name: SharedString,
|
||||
focus_handle: FocusHandle,
|
||||
#[allow(dead_code)]
|
||||
subscriptions: SmallVec<[Subscription; 2]>,
|
||||
_subscriptions: SmallVec<[Subscription; 2]>,
|
||||
_tasks: SmallVec<[Task<()>; 1]>,
|
||||
}
|
||||
|
||||
impl Onboarding {
|
||||
@@ -104,10 +104,11 @@ impl Onboarding {
|
||||
nostr_connect,
|
||||
nostr_connect_uri,
|
||||
qr_code,
|
||||
subscriptions,
|
||||
connecting: false,
|
||||
name: "Onboarding".into(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
_subscriptions: subscriptions,
|
||||
_tasks: smallvec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,36 +132,37 @@ impl Onboarding {
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let client = nostr_client();
|
||||
let connect = this.read_with(cx, |this, cx| this.nostr_connect.read(cx).clone());
|
||||
self._tasks.push(
|
||||
// Wait for Nostr Connect approval
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let client = nostr_client();
|
||||
let connect = this.read_with(cx, |this, cx| this.nostr_connect.read(cx).clone());
|
||||
|
||||
if let Ok(Some(signer)) = connect {
|
||||
match signer.bunker_uri().await {
|
||||
Ok(uri) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_connecting(cx);
|
||||
this.write_uri_to_disk(&uri, cx);
|
||||
})
|
||||
.ok();
|
||||
if let Ok(Some(signer)) = connect {
|
||||
match signer.bunker_uri().await {
|
||||
Ok(uri) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_connecting(cx);
|
||||
this.write_uri_to_disk(&uri, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Set the client's signer with the current nostr connect instance
|
||||
client.set_signer(signer).await;
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Nostr Connect instance (QR Code) is timeout. TODO: fix this");
|
||||
this.update_in(cx, |_, window, cx| {
|
||||
window.push_notification(
|
||||
Notification::error(e.to_string()).title("Nostr Connect"),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
// Set the client's signer with the current nostr connect instance
|
||||
client.set_signer(signer).await;
|
||||
}
|
||||
Err(e) => {
|
||||
this.update_in(cx, |_, window, cx| {
|
||||
window.push_notification(
|
||||
Notification::error(e.to_string()).title("Nostr Connect"),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
|
||||
@@ -11,9 +11,7 @@ use registry::room::RoomKind;
|
||||
use registry::Registry;
|
||||
use settings::AppSettings;
|
||||
use theme::ActiveTheme;
|
||||
use ui::actions::OpenProfile;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::context_menu::ContextMenuExt;
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::skeleton::Skeleton;
|
||||
use ui::{h_flex, ContextModal, StyledExt};
|
||||
@@ -167,10 +165,6 @@ impl RenderOnce for RoomListItem {
|
||||
.child(created_at),
|
||||
),
|
||||
)
|
||||
.context_menu(move |this, _window, _cx| {
|
||||
// TODO: add share chat room
|
||||
this.menu(t!("profile.view"), Box::new(OpenProfile(public_key)))
|
||||
})
|
||||
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
||||
.on_click(move |event, window, cx| {
|
||||
handler(event, window, cx);
|
||||
|
||||
@@ -176,12 +176,12 @@ impl AppSettings {
|
||||
!self.setting_values.authenticated_relays.is_empty() && self.setting_values.auto_auth
|
||||
}
|
||||
|
||||
pub fn is_authenticated_relays(&self, url: &RelayUrl) -> bool {
|
||||
pub fn is_authenticated(&self, url: &RelayUrl) -> bool {
|
||||
self.setting_values.authenticated_relays.contains(url)
|
||||
}
|
||||
|
||||
pub fn push_relay(&mut self, relay_url: &RelayUrl, cx: &mut Context<Self>) {
|
||||
if !self.is_authenticated_relays(relay_url) {
|
||||
if !self.is_authenticated(relay_url) {
|
||||
self.setting_values
|
||||
.authenticated_relays
|
||||
.push(relay_url.to_owned());
|
||||
|
||||
Reference in New Issue
Block a user