From d448caa369ff368886c0780b18daba91131f0192 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 28 Aug 2025 16:26:36 -0400 Subject: [PATCH 1/4] add `AnimationHelper::scaled_rect` Signed-off-by: kernelkind --- crates/notedeck_ui/src/anim.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/notedeck_ui/src/anim.rs b/crates/notedeck_ui/src/anim.rs index e344803c..a1b22e8c 100644 --- a/crates/notedeck_ui/src/anim.rs +++ b/crates/notedeck_ui/src/anim.rs @@ -1,4 +1,4 @@ -use egui::{Pos2, Rect, Response, Sense}; +use egui::{vec2, Pos2, Rect, Response, Sense}; pub fn hover_expand( ui: &mut egui::Ui, @@ -116,6 +116,16 @@ impl AnimationHelper { self.rect } + pub fn scaled_rect(&self) -> egui::Rect { + let min_height = self.rect.height() * (1.0 / self.expansion_multiple); + let min_width = self.rect.width() * (1.0 / self.expansion_multiple); + + egui::Rect::from_center_size( + self.center, + vec2(self.scale_1d_pos(min_width), self.scale_1d_pos(min_height)), + ) + } + pub fn center(&self) -> Pos2 { self.rect.center() } From af4b89673998ad7250bf6a4a67ad6152cba273f7 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 28 Aug 2025 16:27:08 -0400 Subject: [PATCH 2/4] add copy to clipboard img Signed-off-by: kernelkind --- assets/icons/copy-to-clipboard.svg | 3 +++ crates/notedeck_ui/src/app_images.rs | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 assets/icons/copy-to-clipboard.svg diff --git a/assets/icons/copy-to-clipboard.svg b/assets/icons/copy-to-clipboard.svg new file mode 100644 index 00000000..dc2b0723 --- /dev/null +++ b/assets/icons/copy-to-clipboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/notedeck_ui/src/app_images.rs b/crates/notedeck_ui/src/app_images.rs index cbbf58d8..5f1a44a1 100644 --- a/crates/notedeck_ui/src/app_images.rs +++ b/crates/notedeck_ui/src/app_images.rs @@ -244,3 +244,13 @@ pub fn zap_light_image() -> Image<'static> { pub fn like_image() -> Image<'static> { Image::new(include_image!("../../../assets/icons/like_icon_4x.png")) } + +pub fn copy_to_clipboard_image() -> Image<'static> { + Image::new(include_image!( + "../../../assets/icons/copy-to-clipboard.svg" + )) +} + +pub fn copy_to_clipboard_dark_image() -> Image<'static> { + copy_to_clipboard_image().tint(Color32::BLACK) +} From 408afbda5066d58c49774281dfe030d485072830 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 28 Aug 2025 16:27:32 -0400 Subject: [PATCH 3/4] make eye button public Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/account_login_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/notedeck_columns/src/ui/account_login_view.rs b/crates/notedeck_columns/src/ui/account_login_view.rs index 5cc9ec09..9de36f68 100644 --- a/crates/notedeck_columns/src/ui/account_login_view.rs +++ b/crates/notedeck_columns/src/ui/account_login_view.rs @@ -157,7 +157,7 @@ fn login_textedit<'a>( text_edit } -fn eye_button(ui: &mut egui::Ui, is_visible: bool) -> egui::Response { +pub fn eye_button(ui: &mut egui::Ui, is_visible: bool) -> egui::Response { let is_dark_mode = ui.visuals().dark_mode; let icon = if is_visible && is_dark_mode { app_images::eye_dark_image() From 5539e4ef82706a3fd50f9da44b56f0f06ad6338d Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 28 Aug 2025 16:27:51 -0400 Subject: [PATCH 4/4] add keys section to settings Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/settings.rs | 165 ++++++++++++++++++++- 1 file changed, 162 insertions(+), 3 deletions(-) diff --git a/crates/notedeck_columns/src/ui/settings.rs b/crates/notedeck_columns/src/ui/settings.rs index a7644732..ef0ac444 100644 --- a/crates/notedeck_columns/src/ui/settings.rs +++ b/crates/notedeck_columns/src/ui/settings.rs @@ -1,6 +1,8 @@ use egui::{ - vec2, Button, Color32, ComboBox, FontId, Frame, Margin, RichText, ScrollArea, ThemePreference, + vec2, Button, Color32, ComboBox, CornerRadius, FontId, Frame, Layout, Margin, RichText, + ScrollArea, TextEdit, ThemePreference, }; +use egui_extras::{Size, StripBuilder}; use enostr::NoteId; use nostrdb::Transaction; use notedeck::{ @@ -9,9 +11,12 @@ use notedeck::{ Images, JobsCache, LanguageIdentifier, Localization, NoteContext, NotedeckTextStyle, Settings, SettingsHandler, DEFAULT_NOTE_BODY_FONT_SIZE, }; -use notedeck_ui::{NoteOptions, NoteView}; +use notedeck_ui::{ + app_images::{copy_to_clipboard_dark_image, copy_to_clipboard_image}, + AnimationHelper, NoteOptions, NoteView, +}; -use crate::{nav::RouterAction, Damus, Route}; +use crate::{nav::RouterAction, ui::account_login_view::eye_button, Damus, Route}; const PREVIEW_NOTE_ID: &str = "note1edjc8ggj07hwv77g2405uh6j2jkk5aud22gktxrvc2wnre4vdwgqzlv2gw"; @@ -470,6 +475,149 @@ impl<'a> SettingsView<'a> { action } + fn keys_section(&mut self, ui: &mut egui::Ui) { + let title = tr!( + self.note_context.i18n, + "Keys", + "label for keys setting section" + ); + + settings_group(ui, title, |ui| { + ui.horizontal_wrapped(|ui| { + ui.label( + richtext_small(tr!( + self.note_context.i18n, + "PUBLIC ACCOUNT ID", + "label describing public key" + )) + .color(ui.visuals().gray_out(ui.visuals().text_color())), + ); + }); + + let copy_img = if ui.visuals().dark_mode { + copy_to_clipboard_image() + } else { + copy_to_clipboard_dark_image() + }; + let copy_max_size = vec2(16.0, 16.0); + + if let Some(npub) = self.note_context.accounts.selected_account_pubkey().npub() { + item_frame(ui).show(ui, |ui| { + StripBuilder::new(ui) + .size(Size::exact(24.0)) + .cell_layout(Layout::left_to_right(egui::Align::Center)) + .vertical(|mut strip| { + strip.strip(|builder| { + builder + .size(Size::remainder()) + .size(Size::exact(16.0)) + .cell_layout(Layout::left_to_right(egui::Align::Center)) + .horizontal(|mut strip| { + strip.cell(|ui| { + ui.horizontal_wrapped(|ui| { + ui.label(richtext_small(&npub)); + }); + }); + + strip.cell(|ui| { + let helper = AnimationHelper::new( + ui, + "copy-to-clipboard-npub", + copy_max_size, + ); + + copy_img.paint_at(ui, helper.scaled_rect()); + + if helper.take_animation_response().clicked() { + ui.ctx().copy_text(npub); + } + }); + }); + }); + }); + }); + } + + let Some(filled) = self.note_context.accounts.selected_filled() else { + return; + }; + let Some(mut nsec) = bech32::encode::( + bech32::Hrp::parse_unchecked("nsec"), + &filled.secret_key.secret_bytes(), + ) + .ok() else { + return; + }; + + ui.horizontal_wrapped(|ui| { + ui.label( + richtext_small(tr!( + self.note_context.i18n, + "SECRET ACCOUNT LOGIN KEY", + "label describing secret key" + )) + .color(ui.visuals().gray_out(ui.visuals().text_color())), + ); + }); + + let is_password_id = ui.id().with("is-password"); + let is_password = ui + .ctx() + .data_mut(|d| d.get_temp(is_password_id)) + .unwrap_or(true); + + item_frame(ui).show(ui, |ui| { + StripBuilder::new(ui) + .size(Size::exact(24.0)) + .cell_layout(Layout::left_to_right(egui::Align::Center)) + .vertical(|mut strip| { + strip.strip(|builder| { + builder + .size(Size::remainder()) + .size(Size::exact(48.0)) + .cell_layout(Layout::left_to_right(egui::Align::Center)) + .horizontal(|mut strip| { + strip.cell(|ui| { + if is_password { + ui.add( + TextEdit::singleline(&mut nsec) + .password(is_password) + .interactive(false) + .frame(false), + ); + } else { + ui.horizontal_wrapped(|ui| { + ui.label(richtext_small(&nsec)); + }); + } + }); + + strip.cell(|ui| { + let helper = AnimationHelper::new( + ui, + "copy-to-clipboard-nsec", + copy_max_size, + ); + + copy_img.paint_at(ui, helper.scaled_rect()); + + if helper.take_animation_response().clicked() { + ui.ctx().copy_text(nsec); + } + + if eye_button(ui, is_password).clicked() { + ui.ctx().data_mut(|d| { + d.insert_temp(is_password_id, !is_password) + }); + } + }); + }); + }); + }); + }); + }); + } + fn manage_relays_section(&mut self, ui: &mut egui::Ui) -> Option { let mut action = None; @@ -509,6 +657,10 @@ impl<'a> SettingsView<'a> { ui.add_space(5.0); + self.keys_section(ui); + + ui.add_space(5.0); + if let Some(new_action) = self.other_options_section(ui) { action = Some(new_action); } @@ -542,3 +694,10 @@ pub fn format_size(size_bytes: u64) -> String { format!("{:.2} GB", size / GB) } } + +fn item_frame(ui: &egui::Ui) -> egui::Frame { + Frame::new() + .inner_margin(Margin::same(8)) + .corner_radius(CornerRadius::same(8)) + .fill(ui.visuals().panel_fill) +}