refactor app state
This commit is contained in:
@@ -12,8 +12,8 @@ components = { package = "ui", git = "https://github.com/longbridgeapp/gpui-comp
|
|||||||
reqwest_client = { git = "https://github.com/huacnlee/zed.git", branch = "export-platform-window" }
|
reqwest_client = { git = "https://github.com/huacnlee/zed.git", branch = "export-platform-window" }
|
||||||
|
|
||||||
# Nostr
|
# Nostr
|
||||||
nostr-relay-builder = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-relay-builder = { git = "https://github.com/rust-nostr/nostr", branch = "nip17" }
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", branch = "nip17", features = [
|
||||||
"lmdb",
|
"lmdb",
|
||||||
"all-nips",
|
"all-nips",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -8,5 +8,3 @@ gpui.workspace = true
|
|||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
keyring-search.workspace = true
|
|
||||||
keyring.workspace = true
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use gpui::Global;
|
use gpui::Global;
|
||||||
use keyring::Entry;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use state::get_client;
|
use state::get_client;
|
||||||
|
|
||||||
@@ -18,15 +17,4 @@ impl NostrClient {
|
|||||||
|
|
||||||
Self { client }
|
Self { client }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_account(&self, keys: Keys) -> Result<()> {
|
|
||||||
let public_key = keys.public_key().to_bech32()?;
|
|
||||||
let secret = keys.secret_key().to_secret_hex();
|
|
||||||
let entry = Entry::new("Coop Safe Storage", &public_key)?;
|
|
||||||
|
|
||||||
// Add secret to keyring
|
|
||||||
entry.set_password(&secret)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use std::fs;
|
use std::{fs, time::Duration};
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
pub static CLIENT: OnceCell<Client> = OnceCell::const_new();
|
pub static CLIENT: OnceCell<Client> = OnceCell::const_new();
|
||||||
@@ -17,15 +17,17 @@ pub async fn get_client() -> &'static Client {
|
|||||||
.expect("Database is NOT initialized");
|
.expect("Database is NOT initialized");
|
||||||
|
|
||||||
// Setup Nostr Client
|
// Setup Nostr Client
|
||||||
let client = ClientBuilder::default().database(lmdb).build();
|
let opts = Options::new().gossip(true).timeout(Duration::from_secs(5));
|
||||||
|
let client = ClientBuilder::default().database(lmdb).opts(opts).build();
|
||||||
|
|
||||||
// Add some bootstrap relays
|
// Add some bootstrap relays
|
||||||
let _ = client.add_relay("wss://relay.damus.io").await;
|
let _ = client.add_relay("wss://relay.damus.io").await;
|
||||||
let _ = client.add_relay("wss://relay.primal.net").await;
|
let _ = client.add_relay("wss://relay.primal.net").await;
|
||||||
let _ = client.add_relay("wss://nostr.fmt.wiz.biz").await;
|
let _ = client.add_relay("wss://nostr.fmt.wiz.biz").await;
|
||||||
let _ = client.add_relay("wss://directory.yabu.me").await;
|
let _ = client.add_relay("wss://directory.yabu.me").await;
|
||||||
let _ = client.add_relay("wss://purplepag.es").await;
|
|
||||||
let _ = client.add_relay("wss://user.kindpag.es/").await;
|
let _ = client.add_discovery_relay("wss://user.kindpag.es/").await;
|
||||||
|
let _ = client.add_discovery_relay("wss://purplepag.es").await;
|
||||||
|
|
||||||
// Connect to all relays
|
// Connect to all relays
|
||||||
client.connect().await;
|
client.connect().await;
|
||||||
|
|||||||
@@ -22,4 +22,4 @@ reqwest_client.workspace = true
|
|||||||
|
|
||||||
client = { version = "0.1.0", path = "../client" }
|
client = { version = "0.1.0", path = "../client" }
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
env_logger = "0.11.5"
|
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ actions!(main_menu, [Quit]);
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
env_logger::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
// Initialize nostr client
|
// Initialize nostr client
|
||||||
let nostr = NostrClient::init().await;
|
let nostr = NostrClient::init().await;
|
||||||
// Initializ app state
|
// Initialize app state
|
||||||
let app_state = AppState::new();
|
let app_state = AppState::new();
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
@@ -31,9 +31,8 @@ async fn main() {
|
|||||||
|
|
||||||
// Set custom theme
|
// Set custom theme
|
||||||
let mut theme = Theme::from(ThemeColor::dark());
|
let mut theme = Theme::from(ThemeColor::dark());
|
||||||
// TODO: support light mode
|
// Set dark mode by default
|
||||||
theme.mode = ThemeMode::Dark;
|
theme.mode = ThemeMode::Dark;
|
||||||
// TODO: adjust color set
|
|
||||||
|
|
||||||
// Set app state
|
// Set app state
|
||||||
cx.set_global(theme);
|
cx.set_global(theme);
|
||||||
@@ -43,7 +42,7 @@ async fn main() {
|
|||||||
// Set quit action
|
// Set quit action
|
||||||
cx.on_action(quit);
|
cx.on_action(quit);
|
||||||
|
|
||||||
// Rerender
|
// Refresh
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
|
|
||||||
// Set window size
|
// Set window size
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
use gpui::Global;
|
use gpui::Global;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::utils::get_all_accounts_from_keyring;
|
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub accounts: HashSet<PublicKey>,
|
pub signer: Option<PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Global for AppState {}
|
impl Global for AppState {}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let accounts = get_all_accounts_from_keyring();
|
Self { signer: None }
|
||||||
Self { accounts }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use keyring_search::{Limit, List, Search};
|
use keyring_search::{Limit, List, Search};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
pub fn get_all_accounts_from_keyring() -> HashSet<PublicKey> {
|
pub fn get_all_accounts_from_keyring() -> Vec<PublicKey> {
|
||||||
let search = Search::new().expect("Keyring not working.");
|
let search = Search::new().expect("Keyring not working.");
|
||||||
let results = search.by_service("Coop Safe Storage");
|
let results = search.by_service("Coop Safe Storage");
|
||||||
let list = List::list_credentials(&results, Limit::All);
|
let list = List::list_credentials(&results, Limit::All);
|
||||||
let accounts: HashSet<PublicKey> = list
|
let accounts: Vec<PublicKey> = list
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.filter(|v| v.starts_with("npub1") && !v.ends_with("coop"))
|
.filter(|v| v.starts_with("npub1") && !v.ends_with("coop"))
|
||||||
.filter_map(|i| PublicKey::from_bech32(i).ok())
|
.filter_map(|i| PublicKey::from_bech32(i).ok())
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ pub struct AppView {
|
|||||||
|
|
||||||
impl AppView {
|
impl AppView {
|
||||||
pub fn new(cx: &mut ViewContext<'_, Self>) -> AppView {
|
pub fn new(cx: &mut ViewContext<'_, Self>) -> AppView {
|
||||||
// Onboarding model
|
// Onboarding
|
||||||
let onboarding = cx.new_view(Onboarding::new);
|
let onboarding = cx.new_view(Onboarding::new);
|
||||||
// Chat Space view
|
// Chat Space
|
||||||
let chat_space = cx.new_view(ChatSpace::new);
|
let chat_space = cx.new_view(ChatSpace::new);
|
||||||
|
|
||||||
AppView {
|
AppView {
|
||||||
@@ -27,7 +27,7 @@ impl Render for AppView {
|
|||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let mut content = div().size_full().flex().items_center().justify_center();
|
let mut content = div().size_full().flex().items_center().justify_center();
|
||||||
|
|
||||||
if cx.global::<AppState>().accounts.is_empty() {
|
if cx.global::<AppState>().signer.is_none() {
|
||||||
content = content.child(self.onboarding.clone())
|
content = content.child(self.onboarding.clone())
|
||||||
} else {
|
} else {
|
||||||
content = content.child(self.chat_space.clone())
|
content = content.child(self.chat_space.clone())
|
||||||
|
|||||||
@@ -74,27 +74,11 @@ impl RenderOnce for Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BottomBar {
|
pub struct BottomBar {}
|
||||||
accounts: Vec<Account>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BottomBar {
|
impl BottomBar {
|
||||||
pub fn new(cx: &mut ViewContext<'_, Self>) -> BottomBar {
|
pub fn new(cx: &mut ViewContext<'_, Self>) -> BottomBar {
|
||||||
let state: Vec<PublicKey> = cx
|
BottomBar {}
|
||||||
.global::<AppState>()
|
|
||||||
.accounts
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let win_cx = cx.window_context();
|
|
||||||
|
|
||||||
let accounts = state
|
|
||||||
.into_iter()
|
|
||||||
.map(|pk| Account::new(pk, win_cx))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
BottomBar { accounts }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +92,5 @@ impl Render for BottomBar {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.children(self.accounts.clone())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ pub struct ChatSpace {
|
|||||||
impl ChatSpace {
|
impl ChatSpace {
|
||||||
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
|
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
|
||||||
let navigation = cx.new_view(Navigation::new);
|
let navigation = cx.new_view(Navigation::new);
|
||||||
let bottom_bar = cx.new_view(BottomBar::new);
|
|
||||||
|
|
||||||
let layout = cx.new_view(|cx| {
|
let layout = cx.new_view(|cx| {
|
||||||
h_resizable(cx)
|
h_resizable(cx)
|
||||||
@@ -28,7 +27,6 @@ impl ChatSpace {
|
|||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.child(navigation.clone())
|
.child(navigation.clone())
|
||||||
.child(bottom_bar.clone())
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use components::{
|
|||||||
label::Label,
|
label::Label,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
|
use keyring::Entry;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
@@ -21,17 +22,37 @@ impl Onboarding {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe(&input, move |_, text_input, input_event, cx| {
|
cx.subscribe(&input, move |_, text_input, input_event, cx| {
|
||||||
|
let mut async_cx = cx.to_async();
|
||||||
|
let client = cx.global::<NostrClient>().client;
|
||||||
|
let view_id = cx.parent_view_id();
|
||||||
|
|
||||||
if let InputEvent::PressEnter = input_event {
|
if let InputEvent::PressEnter = input_event {
|
||||||
let content = text_input.read(cx).text().to_string();
|
let content = text_input.read(cx).text().to_string();
|
||||||
|
|
||||||
if let Ok(keys) = Keys::parse(content) {
|
if let Ok(keys) = Keys::parse(content) {
|
||||||
|
cx.foreground_executor()
|
||||||
|
.spawn(async move {
|
||||||
let public_key = keys.public_key();
|
let public_key = keys.public_key();
|
||||||
|
let secret = keys.secret_key().to_secret_hex();
|
||||||
|
|
||||||
if cx.global::<NostrClient>().add_account(keys).is_ok() {
|
let entry =
|
||||||
cx.global_mut::<AppState>().accounts.insert(public_key);
|
Entry::new("Coop Safe Storage", &public_key.to_bech32().unwrap())
|
||||||
cx.notify();
|
.unwrap();
|
||||||
|
|
||||||
|
// Store private key to OS Keyring
|
||||||
|
let _ = entry.set_password(&secret);
|
||||||
|
|
||||||
|
// Update signer
|
||||||
|
client.set_signer(keys).await;
|
||||||
|
|
||||||
|
// Update view
|
||||||
|
async_cx.update_global(|app_state: &mut AppState, cx| {
|
||||||
|
app_state.signer = Some(public_key);
|
||||||
|
cx.notify(view_id);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|||||||
Reference in New Issue
Block a user