feat: Out-of-Box Experience (#12)
* refactor app view * feat: onboarding * add back buttons in onboarding
This commit is contained in:
17
crates/account/Cargo.toml
Normal file
17
crates/account/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "account"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
ui = { path = "../ui" }
|
||||
common = { path = "../common" }
|
||||
global = { path = "../global" }
|
||||
|
||||
gpui.workspace = true
|
||||
nostr-sdk.workspace = true
|
||||
anyhow.workspace = true
|
||||
smol.workspace = true
|
||||
smallvec.workspace = true
|
||||
log.workspace = true
|
||||
166
crates/account/src/lib.rs
Normal file
166
crates/account/src/lib.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use common::profile::NostrProfile;
|
||||
use global::{
|
||||
constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID},
|
||||
get_client,
|
||||
};
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Task, Window};
|
||||
use nostr_sdk::prelude::*;
|
||||
use ui::{notification::Notification, ContextModal};
|
||||
|
||||
struct GlobalAccount(Entity<Account>);
|
||||
|
||||
impl Global for GlobalAccount {}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
Account::set_global(cx.new(|_| Account { profile: None }), cx);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Account {
|
||||
pub profile: Option<NostrProfile>,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn global(cx: &App) -> Entity<Self> {
|
||||
cx.global::<GlobalAccount>().0.clone()
|
||||
}
|
||||
|
||||
pub fn set_global(account: Entity<Self>, cx: &mut App) {
|
||||
cx.set_global(GlobalAccount(account));
|
||||
}
|
||||
|
||||
pub fn login<S>(&mut self, signer: S, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
S: NostrSigner + 'static,
|
||||
{
|
||||
let task: Task<Result<NostrProfile, Error>> = cx.background_spawn(async move {
|
||||
let client = get_client();
|
||||
// Use user's signer for main signer
|
||||
_ = client.set_signer(signer).await;
|
||||
|
||||
// Verify nostr signer and get public key
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
// Fetch user's metadata
|
||||
let metadata = client
|
||||
.fetch_metadata(public_key, Duration::from_secs(2))
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(NostrProfile::new(public_key, metadata))
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
match task.await {
|
||||
Ok(profile) => {
|
||||
cx.update(|_, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.profile = Some(profile);
|
||||
this.subscribe(cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(Notification::error(e.to_string()), cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn new_account(&mut self, metadata: Metadata, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let client = get_client();
|
||||
let keys = Keys::generate();
|
||||
|
||||
let task: Task<Result<NostrProfile, Error>> = cx.background_spawn(async move {
|
||||
let public_key = keys.public_key();
|
||||
// Update signer
|
||||
client.set_signer(keys).await;
|
||||
// Set metadata
|
||||
client.set_metadata(&metadata).await?;
|
||||
|
||||
Ok(NostrProfile::new(public_key, metadata))
|
||||
});
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
if let Ok(profile) = task.await {
|
||||
cx.update(|_, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.profile = Some(profile);
|
||||
this.subscribe(cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
cx.update(|window, cx| {
|
||||
window.push_notification(Notification::error("Failed to create account."), cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn subscribe(&self, cx: &Context<Self>) {
|
||||
let Some(profile) = self.profile.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let client = get_client();
|
||||
let user = profile.public_key;
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
// Create a contact list filter
|
||||
let contacts = Filter::new().kind(Kind::ContactList).author(user).limit(1);
|
||||
|
||||
// Create a user's data filter
|
||||
let data = Filter::new()
|
||||
.author(user)
|
||||
.since(Timestamp::now())
|
||||
.kinds(vec![
|
||||
Kind::Metadata,
|
||||
Kind::ContactList,
|
||||
Kind::InboxRelays,
|
||||
Kind::RelayList,
|
||||
]);
|
||||
|
||||
// Create a filter for getting all gift wrapped events send to current user
|
||||
let msg = Filter::new().kind(Kind::GiftWrap).pubkey(user);
|
||||
|
||||
// Create a filter to continuously receive new messages.
|
||||
let new_msg = Filter::new().kind(Kind::GiftWrap).pubkey(user).limit(0);
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
// Only subscribe to the latest contact list
|
||||
client.subscribe(contacts, Some(opts)).await?;
|
||||
|
||||
// Continuously receive new user's data since now
|
||||
client.subscribe(data, None).await?;
|
||||
|
||||
let sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
||||
client.subscribe_with_id(sub_id, msg, Some(opts)).await?;
|
||||
|
||||
let sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
||||
client.subscribe_with_id(sub_id, new_msg, None).await?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
cx.spawn(|_, _| async move {
|
||||
if let Err(e) = task.await {
|
||||
log::error!("Error: {}", e);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user