Redesign for the v1 stable release #3
135
Cargo.lock
generated
135
Cargo.lock
generated
@@ -94,6 +94,56 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.100"
|
version = "1.0.100"
|
||||||
@@ -1097,6 +1147,46 @@ dependencies = [
|
|||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.57"
|
version = "0.1.57"
|
||||||
@@ -1192,6 +1282,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -3274,6 +3370,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -4246,6 +4348,12 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oneshot"
|
name = "oneshot"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@@ -4508,6 +4616,20 @@ dependencies = [
|
|||||||
"state",
|
"state",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petname"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9cd31dcfdbbd7431a807ef4df6edd6473228e94d5c805e8cf671227a21bad068"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
@@ -6074,6 +6196,7 @@ dependencies = [
|
|||||||
"nostr-connect",
|
"nostr-connect",
|
||||||
"nostr-lmdb",
|
"nostr-lmdb",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
|
"petname",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rustls",
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -6097,6 +6220,12 @@ dependencies = [
|
|||||||
"float-cmp",
|
"float-cmp",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.26.3"
|
version = "0.26.3"
|
||||||
@@ -7102,6 +7231,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
3
assets/icons/shield.svg
Normal file
3
assets/icons/shield.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M20.25 6.94155C20.25 6.08069 19.6991 5.31641 18.8825 5.04418L12.6325 2.96085C12.2219 2.824 11.7781 2.824 11.3675 2.96085L5.11754 5.04418C4.30086 5.31641 3.75 6.08069 3.75 6.94155V11.9124C3.75 16.8848 8 19.25 12 21.4079C16 19.25 20.25 16.8848 20.25 11.9124V6.94155Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 446 B |
@@ -104,7 +104,7 @@ impl ChatRegistry {
|
|||||||
let device_signer = device.read(cx).device_signer.clone();
|
let device_signer = device.read(cx).device_signer.clone();
|
||||||
|
|
||||||
// A flag to indicate if the registry is loading
|
// A flag to indicate if the registry is loading
|
||||||
let tracking_flag = Arc::new(AtomicBool::new(true));
|
let tracking_flag = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
// Channel for communication between nostr and gpui
|
// Channel for communication between nostr and gpui
|
||||||
let (tx, rx) = flume::bounded::<NostrEvent>(2048);
|
let (tx, rx) = flume::bounded::<NostrEvent>(2048);
|
||||||
@@ -166,7 +166,7 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
rooms: vec![],
|
rooms: vec![],
|
||||||
loading: true,
|
loading: false,
|
||||||
sender: tx.clone(),
|
sender: tx.clone(),
|
||||||
tracking_flag,
|
tracking_flag,
|
||||||
tracking: None,
|
tracking: None,
|
||||||
@@ -253,37 +253,18 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Tracking the status of unwrapping gift wrap events.
|
/// Tracking the status of unwrapping gift wrap events.
|
||||||
fn tracking(&mut self, cx: &mut Context<Self>) {
|
fn tracking(&mut self, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
|
|
||||||
let status = self.tracking_flag.clone();
|
let status = self.tracking_flag.clone();
|
||||||
let tx = self.sender.clone();
|
let tx = self.sender.clone();
|
||||||
|
|
||||||
self.tracking = Some(cx.background_spawn(async move {
|
self.tracking = Some(cx.background_spawn(async move {
|
||||||
let loop_duration = Duration::from_secs(12);
|
let loop_duration = Duration::from_secs(12);
|
||||||
let mut total_loops = 0;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if client.has_signer().await {
|
|
||||||
total_loops += 1;
|
|
||||||
|
|
||||||
if status.load(Ordering::Acquire) {
|
if status.load(Ordering::Acquire) {
|
||||||
// Reset gift wrap processing flag
|
_ = status.compare_exchange(true, false, Ordering::Release, Ordering::Relaxed);
|
||||||
_ = status.compare_exchange(
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
Ordering::Release,
|
|
||||||
Ordering::Relaxed,
|
|
||||||
);
|
|
||||||
tx.send_async(NostrEvent::Unwrapping(true)).await.ok();
|
tx.send_async(NostrEvent::Unwrapping(true)).await.ok();
|
||||||
} else {
|
} else {
|
||||||
// Wait at least 2 loops to prevent exiting early while events are still being processed
|
|
||||||
if total_loops >= 2 {
|
|
||||||
tx.send_async(NostrEvent::Unwrapping(false)).await.ok();
|
tx.send_async(NostrEvent::Unwrapping(false)).await.ok();
|
||||||
// Reset the counter
|
|
||||||
total_loops = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
smol::Timer::after(loop_duration).await;
|
smol::Timer::after(loop_duration).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const SECONDS_IN_MINUTE: i64 = 60;
|
|||||||
const MINUTES_IN_HOUR: i64 = 60;
|
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;
|
||||||
|
const IMAGE_RESIZER: &str = "https://wsrv.nl";
|
||||||
|
|
||||||
pub trait RenderedProfile {
|
pub trait RenderedProfile {
|
||||||
fn avatar(&self) -> SharedString;
|
fn avatar(&self) -> SharedString;
|
||||||
@@ -24,7 +25,12 @@ impl RenderedProfile for Profile {
|
|||||||
.picture
|
.picture
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|picture| !picture.is_empty())
|
.filter(|picture| !picture.is_empty())
|
||||||
.map(|picture| picture.into())
|
.map(|picture| {
|
||||||
|
let url = format!(
|
||||||
|
"{IMAGE_RESIZER}/?url={picture}&w=100&h=100&fit=cover&mask=circle&n=-1"
|
||||||
|
);
|
||||||
|
url.into()
|
||||||
|
})
|
||||||
.unwrap_or_else(|| "brand/avatar.png".into())
|
.unwrap_or_else(|| "brand/avatar.png".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,510 +0,0 @@
|
|||||||
use std::ops::Range;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
|
||||||
use chat::{ChatRegistry, Room};
|
|
||||||
use common::{TextUtils, BOOTSTRAP_RELAYS};
|
|
||||||
use gpui::prelude::FluentBuilder;
|
|
||||||
use gpui::{
|
|
||||||
div, px, relative, rems, uniform_list, App, AppContext, Context, Entity, InteractiveElement,
|
|
||||||
IntoElement, ParentElement, Render, RetainAllImageCache, SharedString,
|
|
||||||
StatefulInteractiveElement, Styled, Subscription, Task, Window,
|
|
||||||
};
|
|
||||||
use gpui_tokio::Tokio;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use person::PersonRegistry;
|
|
||||||
use smallvec::{smallvec, SmallVec};
|
|
||||||
use state::{NostrAddress, NostrRegistry};
|
|
||||||
use theme::ActiveTheme;
|
|
||||||
use ui::avatar::Avatar;
|
|
||||||
use ui::button::{Button, ButtonVariants};
|
|
||||||
use ui::input::{InputEvent, InputState, TextInput};
|
|
||||||
use ui::modal::ModalButtonProps;
|
|
||||||
use ui::notification::Notification;
|
|
||||||
use ui::{h_flex, v_flex, Disableable, Icon, IconName, Sizable, StyledExt, WindowExtension};
|
|
||||||
|
|
||||||
pub fn compose_button() -> impl IntoElement {
|
|
||||||
div().child(
|
|
||||||
Button::new("compose")
|
|
||||||
.icon(IconName::Plus)
|
|
||||||
.ghost_alt()
|
|
||||||
.cta()
|
|
||||||
.small()
|
|
||||||
.rounded()
|
|
||||||
.on_click(move |_, window, cx| {
|
|
||||||
let compose = cx.new(|cx| Compose::new(window, cx));
|
|
||||||
let weak_view = compose.downgrade();
|
|
||||||
|
|
||||||
window.open_modal(cx, move |modal, _window, cx| {
|
|
||||||
let weak_view = weak_view.clone();
|
|
||||||
let label = if compose.read(cx).selected(cx).len() > 1 {
|
|
||||||
SharedString::from("Create Group DM")
|
|
||||||
} else {
|
|
||||||
SharedString::from("Create DM")
|
|
||||||
};
|
|
||||||
|
|
||||||
modal
|
|
||||||
.alert()
|
|
||||||
.overlay_closable(true)
|
|
||||||
.keyboard(true)
|
|
||||||
.show_close(true)
|
|
||||||
.button_props(ModalButtonProps::default().ok_text(label))
|
|
||||||
.title(SharedString::from("Direct Messages"))
|
|
||||||
.child(compose.clone())
|
|
||||||
.on_ok(move |_, window, cx| {
|
|
||||||
weak_view
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.submit(window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
// false to prevent the modal from closing
|
|
||||||
false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
struct Contact {
|
|
||||||
public_key: PublicKey,
|
|
||||||
selected: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<PublicKey> for Contact {
|
|
||||||
fn as_ref(&self) -> &PublicKey {
|
|
||||||
&self.public_key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Contact {
|
|
||||||
pub fn new(public_key: PublicKey) -> Self {
|
|
||||||
Self {
|
|
||||||
public_key,
|
|
||||||
selected: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected(mut self) -> Self {
|
|
||||||
self.selected = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Compose {
|
|
||||||
/// Input for the room's subject
|
|
||||||
title_input: Entity<InputState>,
|
|
||||||
|
|
||||||
/// Input for the room's members
|
|
||||||
user_input: Entity<InputState>,
|
|
||||||
|
|
||||||
/// User's contacts
|
|
||||||
contacts: Entity<Vec<Contact>>,
|
|
||||||
|
|
||||||
/// Error message
|
|
||||||
error_message: Entity<Option<SharedString>>,
|
|
||||||
|
|
||||||
image_cache: Entity<RetainAllImageCache>,
|
|
||||||
_subscriptions: SmallVec<[Subscription; 2]>,
|
|
||||||
_tasks: SmallVec<[Task<()>; 1]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compose {
|
|
||||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
|
|
||||||
let contacts = cx.new(|_| vec![]);
|
|
||||||
let error_message = cx.new(|_| None);
|
|
||||||
|
|
||||||
let user_input =
|
|
||||||
cx.new(|cx| InputState::new(window, cx).placeholder("npub or nprofile..."));
|
|
||||||
|
|
||||||
let title_input =
|
|
||||||
cx.new(|cx| InputState::new(window, cx).placeholder("Family...(Optional)"));
|
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
|
||||||
let mut tasks = smallvec![];
|
|
||||||
|
|
||||||
let get_contacts: Task<Result<Vec<Contact>, Error>> = cx.background_spawn(async move {
|
|
||||||
let signer = client.signer().await?;
|
|
||||||
let public_key = signer.get_public_key().await?;
|
|
||||||
let profiles = client.database().contacts(public_key).await?;
|
|
||||||
let contacts: Vec<Contact> = profiles
|
|
||||||
.into_iter()
|
|
||||||
.map(|profile| Contact::new(profile.public_key()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(contacts)
|
|
||||||
});
|
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
// Load all contacts
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
|
||||||
match get_contacts.await {
|
|
||||||
Ok(contacts) => {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.extend_contacts(contacts, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
cx.update(|window, cx| {
|
|
||||||
window.push_notification(Notification::error(e.to_string()), cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
subscriptions.push(
|
|
||||||
// Clear the image cache when sidebar is closed
|
|
||||||
cx.on_release_in(window, move |this, window, cx| {
|
|
||||||
this.image_cache.update(cx, |this, cx| {
|
|
||||||
this.clear(window, cx);
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
subscriptions.push(
|
|
||||||
// Handle Enter event for user input
|
|
||||||
cx.subscribe_in(
|
|
||||||
&user_input,
|
|
||||||
window,
|
|
||||||
move |this, _input, event, window, cx| {
|
|
||||||
if let InputEvent::PressEnter { .. } = event {
|
|
||||||
this.add_and_select_contact(window, cx)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
title_input,
|
|
||||||
user_input,
|
|
||||||
error_message,
|
|
||||||
contacts,
|
|
||||||
image_cache: RetainAllImageCache::new(cx),
|
|
||||||
_subscriptions: subscriptions,
|
|
||||||
_tasks: tasks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
|
||||||
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
|
||||||
|
|
||||||
client
|
|
||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extend_contacts<I>(&mut self, contacts: I, cx: &mut Context<Self>)
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = Contact>,
|
|
||||||
{
|
|
||||||
self.contacts.update(cx, |this, cx| {
|
|
||||||
this.extend(contacts);
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_contact(&mut self, contact: Contact, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
let pk = contact.public_key;
|
|
||||||
|
|
||||||
if !self.contacts.read(cx).iter().any(|c| c.public_key == pk) {
|
|
||||||
self._tasks.push(cx.background_spawn(async move {
|
|
||||||
Self::request_metadata(&client, pk).await.ok();
|
|
||||||
}));
|
|
||||||
|
|
||||||
cx.defer_in(window, |this, window, cx| {
|
|
||||||
this.contacts.update(cx, |this, cx| {
|
|
||||||
this.insert(0, contact);
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
this.user_input.update(cx, |this, cx| {
|
|
||||||
this.set_value("", window, cx);
|
|
||||||
this.set_loading(false, cx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.set_error("Contact already added", cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_contact(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
|
|
||||||
self.contacts.update(cx, |this, cx| {
|
|
||||||
if let Some(contact) = this.iter_mut().find(|c| c.public_key == public_key) {
|
|
||||||
contact.selected = true;
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_and_select_contact(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let content = self.user_input.read(cx).value().to_string();
|
|
||||||
let http_client = cx.http_client();
|
|
||||||
|
|
||||||
// Show loading indicator in the input
|
|
||||||
self.user_input.update(cx, |this, cx| {
|
|
||||||
this.set_loading(true, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Ok(public_key) = content.to_public_key() {
|
|
||||||
let contact = Contact::new(public_key).selected();
|
|
||||||
self.push_contact(contact, window, cx);
|
|
||||||
} else if let Ok(addr) = Nip05Address::parse(&content) {
|
|
||||||
let task = Tokio::spawn(cx, async move {
|
|
||||||
if let Ok(profile) = addr.profile(&http_client).await {
|
|
||||||
let public_key = profile.public_key;
|
|
||||||
let contact = Contact::new(public_key).selected();
|
|
||||||
|
|
||||||
Ok(contact)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Not found"))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
|
||||||
match task.await {
|
|
||||||
Ok(Ok(contact)) => {
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
|
||||||
this.push_contact(contact, window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_error(e.to_string(), cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Tokio error: {e}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected(&self, cx: &App) -> Vec<PublicKey> {
|
|
||||||
self.contacts
|
|
||||||
.read(cx)
|
|
||||||
.iter()
|
|
||||||
.filter_map(|contact| {
|
|
||||||
if contact.selected {
|
|
||||||
Some(contact.public_key)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn submit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let chat = ChatRegistry::global(cx);
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let public_key = nostr.read(cx).identity().read(cx).public_key();
|
|
||||||
|
|
||||||
let receivers: Vec<PublicKey> = self.selected(cx);
|
|
||||||
let subject_input = self.title_input.read(cx).value();
|
|
||||||
let subject = (!subject_input.is_empty()).then(|| subject_input.to_string());
|
|
||||||
|
|
||||||
if !self.user_input.read(cx).value().is_empty() {
|
|
||||||
self.add_and_select_contact(window, cx);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
chat.update(cx, |this, cx| {
|
|
||||||
let room = cx.new(|_| Room::new(subject, public_key, receivers));
|
|
||||||
this.emit_room(room.downgrade(), cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.close_modal(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_error(&mut self, error: impl Into<SharedString>, cx: &mut Context<Self>) {
|
|
||||||
// Unlock the user input
|
|
||||||
self.user_input.update(cx, |this, cx| {
|
|
||||||
this.set_loading(false, cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update error message
|
|
||||||
self.error_message.update(cx, |this, cx| {
|
|
||||||
*this = Some(error.into());
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dismiss error after 2 seconds
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.error_message.update(cx, |this, cx| {
|
|
||||||
*this = None;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_items(&self, range: Range<usize>, cx: &Context<Self>) -> Vec<impl IntoElement> {
|
|
||||||
let persons = PersonRegistry::global(cx);
|
|
||||||
let mut items = Vec::with_capacity(self.contacts.read(cx).len());
|
|
||||||
|
|
||||||
for ix in range {
|
|
||||||
let Some(contact) = self.contacts.read(cx).get(ix) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let public_key = contact.public_key;
|
|
||||||
let profile = persons.read(cx).get(&public_key, cx);
|
|
||||||
|
|
||||||
items.push(
|
|
||||||
h_flex()
|
|
||||||
.id(ix)
|
|
||||||
.px_2()
|
|
||||||
.h_11()
|
|
||||||
.w_full()
|
|
||||||
.justify_between()
|
|
||||||
.rounded(cx.theme().radius)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1p5()
|
|
||||||
.text_sm()
|
|
||||||
.child(Avatar::new(profile.avatar()).size(rems(1.75)))
|
|
||||||
.child(profile.name()),
|
|
||||||
)
|
|
||||||
.when(contact.selected, |this| {
|
|
||||||
this.child(
|
|
||||||
Icon::new(IconName::CheckCircle)
|
|
||||||
.small()
|
|
||||||
.text_color(cx.theme().text_accent),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
|
||||||
.on_click(cx.listener(move |this, _, _window, cx| {
|
|
||||||
this.select_contact(public_key, cx);
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Compose {
|
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let error = self.error_message.read(cx).as_ref();
|
|
||||||
let loading = self.user_input.read(cx).loading;
|
|
||||||
let contacts = self.contacts.read(cx);
|
|
||||||
|
|
||||||
v_flex()
|
|
||||||
.image_cache(self.image_cache.clone())
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_sm()
|
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.child(SharedString::from("Start a conversation with someone using their npub or NIP-05 (like foo@bar.com).")),
|
|
||||||
)
|
|
||||||
.when_some(error, |this, msg| {
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.italic()
|
|
||||||
.text_sm()
|
|
||||||
.text_color(cx.theme().danger_foreground)
|
|
||||||
.child(msg.clone()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.h_10()
|
|
||||||
.border_b_1()
|
|
||||||
.border_color(cx.theme().border)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_sm()
|
|
||||||
.font_semibold()
|
|
||||||
.child(SharedString::from("Subject:")),
|
|
||||||
)
|
|
||||||
.child(TextInput::new(&self.title_input).small().appearance(false)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.pt_1()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_sm()
|
|
||||||
.font_semibold()
|
|
||||||
.child(SharedString::from("To:")),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
TextInput::new(&self.user_input)
|
|
||||||
.small()
|
|
||||||
.disabled(loading)
|
|
||||||
.suffix(
|
|
||||||
Button::new("add")
|
|
||||||
.icon(IconName::PlusCircle)
|
|
||||||
.transparent()
|
|
||||||
.small()
|
|
||||||
.disabled(loading)
|
|
||||||
.on_click(cx.listener(move |this, _, window, cx| {
|
|
||||||
this.add_and_select_contact(window, cx);
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map(|this| {
|
|
||||||
if contacts.is_empty() {
|
|
||||||
this.child(
|
|
||||||
v_flex()
|
|
||||||
.h_24()
|
|
||||||
.w_full()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.text_center()
|
|
||||||
.text_xs()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.font_semibold()
|
|
||||||
.line_height(relative(1.2))
|
|
||||||
.child(SharedString::from("No contacts")),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.child(SharedString::from("Your recently contacts will appear here.")),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.child(
|
|
||||||
uniform_list(
|
|
||||||
"contacts",
|
|
||||||
contacts.len(),
|
|
||||||
cx.processor(move |this, range, _window, cx| {
|
|
||||||
this.list_items(range, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.h(px(300.)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -114,7 +114,6 @@ impl Render for GreeterPanel {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.w_full()
|
.w_full()
|
||||||
.items_start()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
@@ -127,17 +126,16 @@ impl Render for GreeterPanel {
|
|||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
|
||||||
.items_start()
|
|
||||||
.justify_start()
|
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
.w_full()
|
||||||
.when(relay_list_state == RelayState::NotSet, |this| {
|
.when(relay_list_state == RelayState::NotSet, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
Button::new("relaylist")
|
Button::new("relaylist")
|
||||||
.icon(Icon::new(IconName::Door))
|
.icon(Icon::new(IconName::Relay))
|
||||||
.label("Set up relay list")
|
.label("Set up relay list")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
|
.no_center()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
relay_list::init(window, cx),
|
relay_list::init(window, cx),
|
||||||
@@ -153,10 +151,11 @@ impl Render for GreeterPanel {
|
|||||||
|this| {
|
|this| {
|
||||||
this.child(
|
this.child(
|
||||||
Button::new("import")
|
Button::new("import")
|
||||||
.icon(Icon::new(IconName::Usb))
|
.icon(Icon::new(IconName::Relay))
|
||||||
.label("Set up messaging relays")
|
.label("Set up messaging relays")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
|
.no_center()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
messaging_relays::init(window, cx),
|
messaging_relays::init(window, cx),
|
||||||
@@ -176,7 +175,6 @@ impl Render for GreeterPanel {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.w_full()
|
.w_full()
|
||||||
.items_start()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
@@ -189,16 +187,15 @@ impl Render for GreeterPanel {
|
|||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
|
||||||
.items_start()
|
|
||||||
.justify_start()
|
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
Button::new("connect")
|
Button::new("connect")
|
||||||
.icon(Icon::new(IconName::Door))
|
.icon(Icon::new(IconName::Door))
|
||||||
.label("Connect account via Nostr Connect")
|
.label("Connect account via Nostr Connect")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
|
.no_center()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
connect::init(window, cx),
|
connect::init(window, cx),
|
||||||
@@ -214,6 +211,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Import a secret key or bunker")
|
.label("Import a secret key or bunker")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
|
.no_center()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
import::init(window, cx),
|
import::init(window, cx),
|
||||||
@@ -230,7 +228,6 @@ impl Render for GreeterPanel {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.w_full()
|
.w_full()
|
||||||
.items_start()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
@@ -243,16 +240,23 @@ impl Render for GreeterPanel {
|
|||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
|
||||||
.items_start()
|
|
||||||
.justify_start()
|
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
Button::new("backup")
|
||||||
|
.icon(Icon::new(IconName::Shield))
|
||||||
|
.label("Backup account")
|
||||||
|
.ghost()
|
||||||
|
.small()
|
||||||
|
.no_center(),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("profile")
|
Button::new("profile")
|
||||||
.icon(Icon::new(IconName::Profile))
|
.icon(Icon::new(IconName::Profile))
|
||||||
.label("Update profile")
|
.label("Update profile")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
|
.no_center()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
profile::init(window, cx),
|
profile::init(window, cx),
|
||||||
@@ -262,19 +266,13 @@ impl Render for GreeterPanel {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
Button::new("changelog")
|
|
||||||
.icon(Icon::new(IconName::Ship))
|
|
||||||
.label("Keep up to date")
|
|
||||||
.ghost()
|
|
||||||
.small(),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("invite")
|
Button::new("invite")
|
||||||
.icon(Icon::new(IconName::Invite))
|
.icon(Icon::new(IconName::Invite))
|
||||||
.label("Invite people")
|
.label("Invite friends")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small(),
|
.small()
|
||||||
|
.no_center(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,3 +23,4 @@ serde.workspace = true
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
||||||
rustls = "0.23"
|
rustls = "0.23"
|
||||||
|
petname = "2.0.2"
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
|
|||||||
pub const DEVICE_GIFTWRAP: &str = "device-gift-wraps";
|
pub const DEVICE_GIFTWRAP: &str = "device-gift-wraps";
|
||||||
/// Default subscription id for user gift wrap events
|
/// Default subscription id for user gift wrap events
|
||||||
pub const USER_GIFTWRAP: &str = "user-gift-wraps";
|
pub const USER_GIFTWRAP: &str = "user-gift-wraps";
|
||||||
|
/// Default avatar for new users
|
||||||
|
pub const DEFAULT_AVATAR: &str = "https://image.nostr.build/93bb6084457a42620849b6827f3f34f111ae5a4ac728638a989d4ed4b4bb3ac8.png";
|
||||||
/// Default vertex relays
|
/// Default vertex relays
|
||||||
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
|
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
|
||||||
/// Default search relays
|
/// Default search relays
|
||||||
@@ -726,7 +728,12 @@ impl NostrRegistry {
|
|||||||
|
|
||||||
/// Create a new identity
|
/// Create a new identity
|
||||||
fn create_identity(&mut self, cx: &mut Context<Self>) {
|
fn create_identity(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let client = self.client();
|
||||||
|
|
||||||
|
// Generate new keys
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
|
|
||||||
|
// Get write credential task
|
||||||
let write_credential = cx.write_credentials(
|
let write_credential = cx.write_credentials(
|
||||||
CLIENT_NAME,
|
CLIENT_NAME,
|
||||||
&keys.public_key().to_hex(),
|
&keys.public_key().to_hex(),
|
||||||
@@ -736,10 +743,23 @@ impl NostrRegistry {
|
|||||||
// Update the signer
|
// Update the signer
|
||||||
self.set_signer(keys, false, cx);
|
self.set_signer(keys, false, cx);
|
||||||
|
|
||||||
// TODO: set metadata
|
// Spawn a task to set metadata and write the credentials
|
||||||
|
|
||||||
// Spawn a task to write the credentials
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
|
let name = petname::petname(2, "-").unwrap_or("Cooper".to_string());
|
||||||
|
let avatar = Url::parse(DEFAULT_AVATAR).unwrap();
|
||||||
|
|
||||||
|
// Construct metadata for the identity
|
||||||
|
let metadata = Metadata::new()
|
||||||
|
.display_name(&name)
|
||||||
|
.name(&name)
|
||||||
|
.picture(avatar);
|
||||||
|
|
||||||
|
// Set metadata for the identity
|
||||||
|
if let Err(e) = client.set_metadata(&metadata).await {
|
||||||
|
log::error!("Failed to set metadata: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the credentials
|
||||||
if let Err(e) = write_credential.await {
|
if let Err(e) = write_credential.await {
|
||||||
log::error!("Failed to write credentials: {}", e);
|
log::error!("Failed to write credentials: {}", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ pub enum IconName {
|
|||||||
Settings,
|
Settings,
|
||||||
Sun,
|
Sun,
|
||||||
Ship,
|
Ship,
|
||||||
|
Shield,
|
||||||
Upload,
|
Upload,
|
||||||
Usb,
|
Usb,
|
||||||
PanelLeft,
|
PanelLeft,
|
||||||
@@ -96,6 +97,7 @@ impl IconName {
|
|||||||
Self::Settings => "icons/settings.svg",
|
Self::Settings => "icons/settings.svg",
|
||||||
Self::Sun => "icons/sun.svg",
|
Self::Sun => "icons/sun.svg",
|
||||||
Self::Ship => "icons/ship.svg",
|
Self::Ship => "icons/ship.svg",
|
||||||
|
Self::Shield => "icons/shield.svg",
|
||||||
Self::Upload => "icons/upload.svg",
|
Self::Upload => "icons/upload.svg",
|
||||||
Self::Usb => "icons/usb.svg",
|
Self::Usb => "icons/usb.svg",
|
||||||
Self::PanelLeft => "icons/panel-left.svg",
|
Self::PanelLeft => "icons/panel-left.svg",
|
||||||
|
|||||||
Reference in New Issue
Block a user