This commit is contained in:
2026-11-04 10:08:58 +07:00
parent 06ff8ce65d
commit 198bd26422
2 changed files with 111 additions and 4 deletions
+102 -3
View File
@@ -1,18 +1,25 @@
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use auto_update::AutoUpdater; use auto_update::AutoUpdater;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{
Anchor, App, Context, Div, InteractiveElement, ParentElement, SharedString, Stateful, Styled,
Window, div, px,
};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use gpui::{AnyElement, Entity, IntoElement}; use gpui::{AnyElement, Entity, IntoElement};
use gpui::{ use gpui_kit::base::StyledExt as _;
App, Context, Div, InteractiveElement, ParentElement, Stateful, Styled, Window, div, px,
};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use gpui_kit::component::Disableable; use gpui_kit::component::Disableable;
use gpui_kit::component::button::{Button, ButtonVariant, ButtonVariants}; use gpui_kit::component::button::{Button, ButtonVariant, ButtonVariants};
use gpui_kit::component::clipboard::Clipboard;
use gpui_kit::component::dialog::DialogButtonProps; use gpui_kit::component::dialog::DialogButtonProps;
use gpui_kit::component::menu::{DropdownMenu, PopupMenuItem}; use gpui_kit::component::menu::{DropdownMenu, PopupMenuItem};
use gpui_kit::component::popover::Popover;
use gpui_kit::component::separator::Separator;
use gpui_kit::component::{ActiveTheme, Icon, Selectable, Sizable, WindowExt, h_flex, v_flex}; use gpui_kit::component::{ActiveTheme, Icon, Selectable, Sizable, WindowExt, h_flex, v_flex};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use person::shorten_pubkey;
use settings::AppSettings;
use state::{CLIENT_NAME, NostrRegistry}; use state::{CLIENT_NAME, NostrRegistry};
use ui::{Avatar, IconName}; use ui::{Avatar, IconName};
@@ -31,6 +38,10 @@ impl Sidebar {
let updater = AutoUpdater::try_global(cx); let updater = AutoUpdater::try_global(cx);
let profile = self.directory.read(cx).person(current_user, cx); let profile = self.directory.read(cx).person(current_user, cx);
let avatar = profile.avatar(); let avatar = profile.avatar();
let encryption_key = profile
.announcement()
.map(|announcement| announcement.public_key());
let encryption_enabled = AppSettings::get_nip4e(cx);
h_flex() h_flex()
.id("sidebar-user") .id("sidebar-user")
@@ -71,6 +82,11 @@ impl Sidebar {
}); });
actions actions
.when(encryption_enabled, |actions| {
actions.when_some(encryption_key, |actions, encryption_key| {
actions.child(self.render_encryption(encryption_key))
})
})
.child( .child(
Button::new("sidebar-search") Button::new("sidebar-search")
.icon(IconName::Search) .icon(IconName::Search)
@@ -111,6 +127,89 @@ impl Sidebar {
}) })
} }
/// The encryption key popover: public key with copy, backup and reset
fn render_encryption(&self, encryption_key: PublicKey) -> impl IntoElement {
Popover::new("sidebar-encryption")
.anchor(Anchor::TopRight)
.trigger(
Button::new("encryption-key")
.icon(IconName::Lock)
.tooltip("Encryption Key")
.ghost()
.small(),
)
.content(move |_, _, cx| {
let popover = cx.entity();
let Ok(npub) = encryption_key.to_bech32();
v_flex()
.w_64()
.gap_2()
.text_sm()
.child(
div()
.text_xs()
.font_semibold()
.text_color(cx.theme().muted_foreground)
.child("Public key"),
)
.child(
h_flex()
.items_center()
.gap_1()
.child(
div()
.flex_1()
.min_w_0()
.truncate()
.text_color(cx.theme().muted_foreground)
.child(SharedString::from(shorten_pubkey(encryption_key, 12))),
)
.child(
Clipboard::new("encryption-key-copy")
.tooltip("Copy public key")
.value(npub),
),
)
.child(Separator::horizontal())
.child(
h_flex()
.justify_end()
.gap_2()
.child(
Button::new("encryption-backup")
.label("Backup")
.outline()
.small()
.on_click({
let popover = popover.clone();
move |_, window, cx| {
popover
.update(cx, |state, cx| state.dismiss(window, cx));
window.dispatch_action(
Box::new(Command::BackupEncryption),
cx,
);
}
}),
)
.child(
Button::new("encryption-reset")
.label("Reset")
.ghost()
.small()
.on_click(move |_, window, cx| {
popover.update(cx, |state, cx| state.dismiss(window, cx));
window.dispatch_action(
Box::new(Command::ResetEncryption),
cx,
);
}),
),
)
})
}
pub(super) fn render_footer(&self, loading: bool, _cx: &mut Context<Self>) -> Div { pub(super) fn render_footer(&self, loading: bool, _cx: &mut Context<Self>) -> Div {
v_flex() v_flex()
.p_2() .p_2()
+9 -1
View File
@@ -21,6 +21,7 @@ use nostr_sdk::prelude::*;
use person::{Directory, PersonRegistry}; use person::{Directory, PersonRegistry};
pub(crate) use rows::open_room; pub(crate) use rows::open_room;
use search::SearchResult; use search::SearchResult;
use settings::AppSettings;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{FIND_DELAY, NostrRegistry, StateEvent}; use state::{FIND_DELAY, NostrRegistry, StateEvent};
use ui::title_bar_drag_handlers; use ui::title_bar_drag_handlers;
@@ -104,6 +105,8 @@ impl Sidebar {
let chat = ChatRegistry::global(cx); let chat = ChatRegistry::global(cx);
let groups = GroupsRegistry::global(cx); let groups = GroupsRegistry::global(cx);
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let settings = AppSettings::global(cx);
let directory = Directory::new(cx);
let find_input = cx.new(|cx| { let find_input = cx.new(|cx| {
InputState::new(window, cx) InputState::new(window, cx)
@@ -206,6 +209,11 @@ impl Sidebar {
}, },
)); ));
// The user header reads settings and the tracked profiles on render, so
// the encryption button reacts to toggles and announcement arrivals
subscriptions.push(cx.observe(&settings, |_, _, cx| cx.notify()));
subscriptions.push(cx.observe(&directory, |_, _, cx| cx.notify()));
tasks.push(cx.spawn(async move |this, cx| { tasks.push(cx.spawn(async move |this, cx| {
let timer = Duration::from_millis(PROGRESS_TICK_MS); let timer = Duration::from_millis(PROGRESS_TICK_MS);
loop { loop {
@@ -229,7 +237,7 @@ impl Sidebar {
chats_open: None, chats_open: None,
contacts_open: false, contacts_open: false,
contacts: None, contacts: None,
directory: Directory::new(cx), directory,
selected_contacts: HashSet::new(), selected_contacts: HashSet::new(),
requests_open: false, requests_open: false,
new_requests: false, new_requests: false,