update encryption panel
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m58s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m39s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled

This commit is contained in:
2026-02-24 15:48:58 +07:00
parent e44ce7e3f6
commit 10ded51d2f
12 changed files with 352 additions and 128 deletions

3
assets/icons/reset.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M13 17.25C16.4518 17.25 19.25 14.4518 19.25 11V10.5C19.25 6.77208 16.2279 3.75 12.5 3.75C8.77208 3.75 5.75 6.77208 5.75 10.5V20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 16.5L5.75 20.25L9.5 16.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 435 B

View File

@@ -13,38 +13,6 @@ const MINUTES_IN_HOUR: i64 = 60;
const HOURS_IN_DAY: i64 = 24; const HOURS_IN_DAY: i64 = 24;
const DAYS_IN_MONTH: i64 = 30; const DAYS_IN_MONTH: i64 = 30;
pub trait RenderedProfile {
fn avatar(&self) -> SharedString;
fn display_name(&self) -> SharedString;
}
impl RenderedProfile for Profile {
fn avatar(&self) -> SharedString {
self.metadata()
.picture
.as_ref()
.filter(|picture| !picture.is_empty())
.map(|picture| picture.into())
.unwrap_or_else(|| "brand/avatar.png".into())
}
fn display_name(&self) -> SharedString {
if let Some(display_name) = self.metadata().display_name.as_ref() {
if !display_name.is_empty() {
return SharedString::from(display_name);
}
}
if let Some(name) = self.metadata().name.as_ref() {
if !name.is_empty() {
return SharedString::from(name);
}
}
SharedString::from(shorten_pubkey(self.public_key(), 4))
}
}
pub trait RenderedTimestamp { pub trait RenderedTimestamp {
fn to_human_time(&self) -> SharedString; fn to_human_time(&self) -> SharedString;
fn to_ago(&self) -> SharedString; fn to_ago(&self) -> SharedString;
@@ -126,13 +94,3 @@ impl<T: AsRef<str>> TextUtils for T {
))) )))
} }
} }
pub fn shorten_pubkey(public_key: PublicKey, len: usize) -> String {
let Ok(pubkey) = public_key.to_bech32();
format!(
"{}:{}",
&pubkey[0..(len + 1)],
&pubkey[pubkey.len() - len..]
)
}

View File

@@ -2,14 +2,14 @@ use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use anyhow::{Context as AnyhowContext, Error}; use anyhow::{Context as AnyhowContext, Error};
use common::{shorten_pubkey, RenderedTimestamp}; use common::RenderedTimestamp;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity, div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
}; };
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry}; use person::{shorten_pubkey, Person, PersonRegistry};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use state::{NostrAddress, NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT}; use state::{NostrAddress, NostrRegistry, BOOTSTRAP_RELAYS, TIMEOUT};
use theme::ActiveTheme; use theme::ActiveTheme;

View File

@@ -1,27 +1,151 @@
use anyhow::Error;
use device::DeviceRegistry;
use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, div, px, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, Render, SharedString, Styled, Window, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
}; };
use nostr_sdk::prelude::*;
use person::{shorten_pubkey, PersonRegistry};
use state::Announcement;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::dock_area::panel::{Panel, PanelEvent}; use ui::dock_area::panel::{Panel, PanelEvent};
use ui::v_flex; use ui::notification::Notification;
use ui::{divider, h_flex, v_flex, Disableable, IconName, Sizable, StyledExt, WindowExtension};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<EncryptionPanel> { const MSG: &str =
cx.new(|cx| EncryptionPanel::new(window, cx)) "Encryption Key is a special key that used to encrypt and decrypt your messages. \
Your identity is completely decoupled from all encryption processes to protect your privacy.";
const NOTICE: &str = "By resetting your encryption key, you will lose access to \
all your encrypted messages before. This action cannot be undone.";
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<EncryptionPanel> {
cx.new(|cx| EncryptionPanel::new(public_key, window, cx))
} }
#[derive(Debug)] #[derive(Debug)]
pub struct EncryptionPanel { pub struct EncryptionPanel {
name: SharedString, name: SharedString,
focus_handle: FocusHandle, focus_handle: FocusHandle,
/// User's public key
public_key: PublicKey,
/// Whether the panel is loading
loading: bool,
/// Tasks
tasks: Vec<Task<Result<(), Error>>>,
} }
impl EncryptionPanel { impl EncryptionPanel {
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(public_key: PublicKey, _window: &mut Window, cx: &mut Context<Self>) -> Self {
Self { Self {
name: "Encryption".into(), name: "Encryption".into(),
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
public_key,
loading: false,
tasks: vec![],
} }
} }
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
self.loading = status;
cx.notify();
}
fn approve(&mut self, event: &Event, window: &mut Window, cx: &mut Context<Self>) {
let device = DeviceRegistry::global(cx);
let task = device.read(cx).approve(event, cx);
let id = event.id;
// Update loading status
self.set_loading(true, cx);
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
match task.await {
Ok(_) => {
this.update_in(cx, |this, window, cx| {
// Reset loading status
this.set_loading(false, cx);
// Remove request
device.update(cx, |this, cx| {
this.remove_request(&id, cx);
});
window.push_notification("Approved", cx);
})?;
}
Err(e) => {
this.update_in(cx, |this, window, cx| {
this.set_loading(false, cx);
window.push_notification(Notification::error(e.to_string()), cx);
})?;
}
}
Ok(())
}));
}
fn render_requests(&mut self, cx: &mut Context<Self>) -> Vec<impl IntoElement> {
const TITLE: &str = "You've requested for the Encryption Key from:";
let device = DeviceRegistry::global(cx);
let requests = device.read(cx).requests.clone();
let mut items = Vec::new();
for event in requests.into_iter() {
let request = Announcement::from(&event);
let client_name = request.client_name();
let target = request.public_key();
items.push(
v_flex()
.gap_2()
.text_sm()
.child(SharedString::from(TITLE))
.child(
v_flex()
.h_12()
.items_center()
.justify_center()
.px_2()
.rounded(cx.theme().radius)
.bg(cx.theme().warning_background)
.text_color(cx.theme().warning_foreground)
.child(client_name.clone()),
)
.child(
h_flex()
.h_7()
.w_full()
.px_2()
.rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background)
.child(SharedString::from(target.to_hex())),
)
.child(
h_flex().justify_end().gap_2().child(
Button::new("approve")
.label("Approve")
.ghost()
.small()
.disabled(self.loading)
.loading(self.loading)
.on_click(cx.listener(move |this, _ev, window, cx| {
this.approve(&event, window, cx);
})),
),
),
)
}
items
}
} }
impl Panel for EncryptionPanel { impl Panel for EncryptionPanel {
@@ -43,12 +167,128 @@ impl Focusable for EncryptionPanel {
} }
impl Render for EncryptionPanel { impl Render for EncryptionPanel {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let device = DeviceRegistry::global(cx);
let state = device.read(cx).state();
let has_requests = device.read(cx).has_requests();
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(&self.public_key, cx);
let Some(announcement) = profile.announcement() else {
return div();
};
let pubkey = SharedString::from(shorten_pubkey(announcement.public_key(), 16));
let client_name = announcement.client_name();
v_flex() v_flex()
.size_full() .p_3()
.gap_3()
.w_full()
.child(
div()
.text_xs()
.text_color(cx.theme().text_muted)
.child(SharedString::from(MSG)),
)
.child(divider(cx))
.child(
v_flex()
.gap_3()
.text_sm()
.child(
v_flex()
.gap_1p5()
.child(
div()
.text_color(cx.theme().text_muted)
.child(SharedString::from("Device Name:")),
)
.child(
h_flex()
.h_12()
.items_center() .items_center()
.justify_center() .justify_center()
.p_2() .rounded(cx.theme().radius)
.gap_10() .bg(cx.theme().elevated_surface_background)
.child(client_name.clone()),
),
)
.child(
v_flex()
.gap_1p5()
.child(
div()
.text_color(cx.theme().text_muted)
.child(SharedString::from("Encryption Public Key:")),
)
.child(
h_flex()
.h_7()
.w_full()
.px_2()
.rounded(cx.theme().radius)
.bg(cx.theme().elevated_surface_background)
.child(pubkey),
),
),
)
.when(has_requests, |this| {
this.child(divider(cx)).child(
v_flex()
.gap_1p5()
.w_full()
.child(
div()
.text_color(cx.theme().text_muted)
.child(SharedString::from("Requests:")),
)
.child(
v_flex()
.gap_2()
.flex_1()
.w_full()
.children(self.render_requests(cx)),
),
)
})
.child(divider(cx))
.when(state.requesting(), |this| {
this.child(
h_flex()
.h_8()
.justify_center()
.text_xs()
.text_center()
.text_color(cx.theme().text_accent)
.bg(cx.theme().elevated_surface_background)
.rounded(cx.theme().radius)
.child(SharedString::from(
"Please open other device and approve the request",
)),
)
})
.when(state.set(), |this| {
this.child(
v_flex()
.gap_1()
.child(
Button::new("reset")
.icon(IconName::Reset)
.label("Reset")
.warning()
.small()
.font_semibold(),
)
.child(
div()
.italic()
.text_size(px(10.))
.text_color(cx.theme().text_muted)
.child(SharedString::from(NOTICE)),
),
)
})
} }
} }

View File

@@ -214,6 +214,7 @@ impl MessagingRelayPanel {
} }
Err(e) => { Err(e) => {
this.update_in(cx, |this, window, cx| { this.update_in(cx, |this, window, cx| {
this.set_updating(false, cx);
this.set_error(e.to_string(), window, cx); this.set_error(e.to_string(), window, cx);
})?; })?;
} }

View File

@@ -2,7 +2,6 @@ use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use common::shorten_pubkey;
use gpui::{ use gpui::{
div, rems, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, div, rems, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter,
FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render, SharedString, FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions, Render, SharedString,
@@ -10,7 +9,7 @@ use gpui::{
}; };
use gpui_tokio::Tokio; use gpui_tokio::Tokio;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry}; use person::{shorten_pubkey, Person, PersonRegistry};
use settings::AppSettings; use settings::AppSettings;
use smol::fs; use smol::fs;
use state::{nostr_upload, NostrRegistry}; use state::{nostr_upload, NostrRegistry};

View File

@@ -234,6 +234,7 @@ impl RelayListPanel {
} }
Err(e) => { Err(e) => {
this.update_in(cx, |this, window, cx| { this.update_in(cx, |this, window, cx| {
this.set_updating(false, cx);
this.set_error(e.to_string(), window, cx); this.set_error(e.to_string(), window, cx);
})?; })?;
} }

View File

@@ -182,15 +182,20 @@ impl Workspace {
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) { fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
match command { match command {
Command::OpenEncryptionPanel => { Command::OpenEncryptionPanel => {
let nostr = NostrRegistry::global(cx);
let signer = nostr.read(cx).signer();
if let Some(public_key) = signer.public_key() {
self.dock.update(cx, |this, cx| { self.dock.update(cx, |this, cx| {
this.add_panel( this.add_panel(
Arc::new(encryption_key::init(window, cx)), Arc::new(encryption_key::init(public_key, window, cx)),
DockPlacement::Right, DockPlacement::Right,
window, window,
cx, cx,
); );
}); });
} }
}
Command::OpenInboxPanel => { Command::OpenInboxPanel => {
self.dock.update(cx, |this, cx| { self.dock.update(cx, |this, cx| {
this.add_panel( this.add_panel(

View File

@@ -25,12 +25,12 @@ impl Global for GlobalDeviceRegistry {}
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md /// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
#[derive(Debug)] #[derive(Debug)]
pub struct DeviceRegistry { pub struct DeviceRegistry {
/// Request for encryption key from other devices
pub requests: Vec<Event>,
/// Device state /// Device state
state: DeviceState, state: DeviceState,
/// Device requests
requests: Entity<HashSet<Event>>,
/// Async tasks /// Async tasks
tasks: Vec<Task<Result<(), Error>>>, tasks: Vec<Task<Result<(), Error>>>,
@@ -52,8 +52,6 @@ impl DeviceRegistry {
/// Create a new device registry instance /// Create a new device registry instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let requests = cx.new(|_| HashSet::default());
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
subscriptions.push( subscriptions.push(
@@ -77,7 +75,7 @@ impl DeviceRegistry {
}); });
Self { Self {
requests, requests: vec![],
state: DeviceState::default(), state: DeviceState::default(),
tasks: vec![], tasks: vec![],
_subscriptions: subscriptions, _subscriptions: subscriptions,
@@ -89,7 +87,7 @@ impl DeviceRegistry {
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let (tx, rx) = flume::bounded::<Event>(100); let (tx, rx) = flume::bounded::<Event>(100);
cx.background_spawn(async move { self.tasks.push(cx.background_spawn(async move {
let mut notifications = client.notifications(); let mut notifications = client.notifications();
let mut processed_events = HashSet::new(); let mut processed_events = HashSet::new();
@@ -107,20 +105,21 @@ impl DeviceRegistry {
match event.kind { match event.kind {
Kind::Custom(4454) => { Kind::Custom(4454) => {
if verify_author(&client, event.as_ref()).await { if verify_author(&client, event.as_ref()).await {
tx.send_async(event.into_owned()).await.ok(); tx.send_async(event.into_owned()).await?;
} }
} }
Kind::Custom(4455) => { Kind::Custom(4455) => {
if verify_author(&client, event.as_ref()).await { if verify_author(&client, event.as_ref()).await {
tx.send_async(event.into_owned()).await.ok(); tx.send_async(event.into_owned()).await?;
} }
} }
_ => {} _ => {}
} }
} }
} }
})
.detach(); Ok(())
}));
self.tasks.push( self.tasks.push(
// Update GPUI states // Update GPUI states
@@ -147,8 +146,8 @@ impl DeviceRegistry {
} }
/// Get the device state /// Get the device state
pub fn state(&self) -> &DeviceState { pub fn state(&self) -> DeviceState {
&self.state self.state.clone()
} }
/// Set the device state /// Set the device state
@@ -181,19 +180,25 @@ impl DeviceRegistry {
/// Reset the device state /// Reset the device state
fn reset(&mut self, cx: &mut Context<Self>) { fn reset(&mut self, cx: &mut Context<Self>) {
self.state = DeviceState::Idle; self.state = DeviceState::Idle;
self.requests.update(cx, |this, cx| { self.requests.clear();
this.clear();
cx.notify();
});
cx.notify(); cx.notify();
} }
/// Add a request for device keys /// Add a request for device keys
fn add_request(&mut self, request: Event, cx: &mut Context<Self>) { fn add_request(&mut self, request: Event, cx: &mut Context<Self>) {
self.requests.update(cx, |this, cx| { self.requests.push(request);
this.insert(request);
cx.notify(); cx.notify();
}); }
/// Remove a request for device keys
pub fn remove_request(&mut self, id: &EventId, cx: &mut Context<Self>) {
self.requests.retain(|r| r.id != *id);
cx.notify();
}
/// Check if there are any pending requests
pub fn has_requests(&self) -> bool {
!self.requests.is_empty()
} }
/// Get all messages for encryption keys /// Get all messages for encryption keys
@@ -290,12 +295,12 @@ impl DeviceRegistry {
match task.await { match task.await {
Ok(event) => { Ok(event) => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.init_device_signer(&event, cx); this.new_signer(&event, cx);
})?; })?;
} }
Err(_) => { Err(_) => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.announce_device(cx); this.announce(cx);
})?; })?;
} }
} }
@@ -305,13 +310,15 @@ impl DeviceRegistry {
} }
/// Create a new device signer and announce it /// Create a new device signer and announce it
fn announce_device(&mut self, cx: &mut Context<Self>) { fn announce(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
// Get current user
let signer = nostr.read(cx).signer(); let signer = nostr.read(cx).signer();
let public_key = signer.public_key().unwrap(); let public_key = signer.public_key().unwrap();
// Get user's write relays
let write_relays = nostr.read(cx).write_relays(&public_key, cx); let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let keys = Keys::generate(); let keys = Keys::generate();
@@ -338,20 +345,20 @@ impl DeviceRegistry {
Ok(()) Ok(())
}); });
cx.spawn(async move |this, cx| { self.tasks.push(cx.spawn(async move |this, cx| {
if task.await.is_ok() { if task.await.is_ok() {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.set_signer(keys, cx); this.set_signer(keys, cx);
this.listen_device_request(cx); this.listen_request(cx);
}) })?;
.ok();
} }
})
.detach(); Ok(())
}));
} }
/// Initialize device signer (decoupled encryption key) for the current user /// Initialize device signer (decoupled encryption key) for the current user
fn init_device_signer(&mut self, event: &Event, cx: &mut Context<Self>) { fn new_signer(&mut self, event: &Event, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
@@ -375,14 +382,14 @@ impl DeviceRegistry {
Ok(keys) => { Ok(keys) => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.set_signer(keys, cx); this.set_signer(keys, cx);
this.listen_device_request(cx); this.listen_request(cx);
}) })
.ok(); .ok();
} }
Err(e) => { Err(e) => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.request_device_keys(cx); this.request(cx);
this.listen_device_approval(cx); this.listen_approval(cx);
}) })
.ok(); .ok();
@@ -394,7 +401,7 @@ impl DeviceRegistry {
} }
/// Listen for device key requests on user's write relays /// Listen for device key requests on user's write relays
fn listen_device_request(&mut self, cx: &mut Context<Self>) { fn listen_request(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
@@ -426,7 +433,7 @@ impl DeviceRegistry {
} }
/// Listen for device key approvals on user's write relays /// Listen for device key approvals on user's write relays
fn listen_device_approval(&mut self, cx: &mut Context<Self>) { fn listen_approval(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
@@ -435,7 +442,7 @@ impl DeviceRegistry {
let write_relays = nostr.read(cx).write_relays(&public_key, cx); let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move { self.tasks.push(cx.background_spawn(async move {
let urls = write_relays.await; let urls = write_relays.await;
// Construct a filter for device key requests // Construct a filter for device key requests
@@ -452,13 +459,11 @@ impl DeviceRegistry {
client.subscribe(target).await?; client.subscribe(target).await?;
Ok(()) Ok(())
}); }));
task.detach();
} }
/// Request encryption keys from other device /// Request encryption keys from other device
fn request_device_keys(&mut self, cx: &mut Context<Self>) { fn request(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
@@ -559,34 +564,32 @@ impl DeviceRegistry {
Ok(keys) Ok(keys)
}); });
cx.spawn(async move |this, cx| { self.tasks.push(cx.spawn(async move |this, cx| {
match task.await { let keys = task.await?;
Ok(keys) => {
// Update signer
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.set_signer(keys, cx); this.set_signer(keys, cx);
}) })?;
.ok();
} Ok(())
Err(e) => { }));
log::error!("Error: {e}")
}
};
})
.detach();
} }
/// Approve requests for device keys from other devices /// Approve requests for device keys from other devices
#[allow(dead_code)] pub fn approve(&self, event: &Event, cx: &App) -> Task<Result<(), Error>> {
fn approve(&mut self, event: Event, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
// Get current user
let signer = nostr.read(cx).signer(); let signer = nostr.read(cx).signer();
let public_key = signer.public_key().unwrap(); let public_key = signer.public_key().unwrap();
// Get user's write relays
let write_relays = nostr.read(cx).write_relays(&public_key, cx); let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let event = event.clone();
let task: Task<Result<(), Error>> = cx.background_spawn(async move { cx.background_spawn(async move {
let urls = write_relays.await; let urls = write_relays.await;
// Get device keys // Get device keys
@@ -619,9 +622,7 @@ impl DeviceRegistry {
client.send_event(&event).to(urls).await?; client.send_event(&event).to(urls).await?;
Ok(()) Ok(())
}); })
task.detach();
} }
} }

View File

@@ -4,7 +4,7 @@ use std::sync::OnceLock;
pub const CLIENT_NAME: &str = "Coop"; pub const CLIENT_NAME: &str = "Coop";
/// COOP's public key /// COOP's public key
pub const COOP_PUBKEY: &str = "npub126kl5fruqan90py77gf6pvfvygefl2mu2ukew6xdx5pc5uqscwgsnkgarv"; pub const COOP_PUBKEY: &str = "npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5wrykwl5ys7wpw3x";
/// App ID /// App ID
pub const APP_ID: &str = "su.reya.coop"; pub const APP_ID: &str = "su.reya.coop";

View File

@@ -9,6 +9,20 @@ pub enum DeviceState {
Set, Set,
} }
impl DeviceState {
pub fn idle(&self) -> bool {
matches!(self, DeviceState::Idle)
}
pub fn requesting(&self) -> bool {
matches!(self, DeviceState::Requesting)
}
pub fn set(&self) -> bool {
matches!(self, DeviceState::Set)
}
}
/// Announcement /// Announcement
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Announcement { pub struct Announcement {

View File

@@ -47,6 +47,7 @@ pub enum IconName {
Plus, Plus,
PlusCircle, PlusCircle,
Profile, Profile,
Reset,
Relay, Relay,
Reply, Reply,
Refresh, Refresh,
@@ -112,6 +113,7 @@ impl IconNamed for IconName {
Self::Plus => "icons/plus.svg", Self::Plus => "icons/plus.svg",
Self::PlusCircle => "icons/plus-circle.svg", Self::PlusCircle => "icons/plus-circle.svg",
Self::Profile => "icons/profile.svg", Self::Profile => "icons/profile.svg",
Self::Reset => "icons/reset.svg",
Self::Relay => "icons/relay.svg", Self::Relay => "icons/relay.svg",
Self::Reply => "icons/reply.svg", Self::Reply => "icons/reply.svg",
Self::Refresh => "icons/refresh.svg", Self::Refresh => "icons/refresh.svg",