Merge profiling editing #625

Changelog-Added: Added profile editing
This commit is contained in:
William Casarin
2025-01-04 13:15:25 -08:00
24 changed files with 893 additions and 209 deletions

View File

@@ -245,15 +245,7 @@ impl<'a> NavTitle<'a> {
TimelineRoute::Quote(_note_id) => {}
TimelineRoute::Profile(pubkey) => {
let txn = Transaction::new(self.ndb).unwrap();
if let Some(pfp) = self.pubkey_pfp(&txn, pubkey.bytes(), pfp_size) {
ui.add(pfp);
} else {
ui.add(
ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url())
.size(pfp_size),
);
}
self.show_profile(ui, pubkey, pfp_size);
}
},
@@ -264,9 +256,23 @@ impl<'a> NavTitle<'a> {
Route::Relays => {}
Route::NewDeck => {}
Route::EditDeck(_) => {}
Route::EditProfile(pubkey) => {
self.show_profile(ui, pubkey, pfp_size);
}
}
}
fn show_profile(&mut self, ui: &mut egui::Ui, pubkey: &Pubkey, pfp_size: f32) {
let txn = Transaction::new(self.ndb).unwrap();
if let Some(pfp) = self.pubkey_pfp(&txn, pubkey.bytes(), pfp_size) {
ui.add(pfp);
} else {
ui.add(
ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url()).size(pfp_size),
);
};
}
fn title_label_value(title: &str) -> egui::Label {
egui::Label::new(RichText::new(title).text_style(NotedeckTextStyle::Body.text_style()))
.selectable(false)

View File

@@ -1,5 +1,5 @@
use crate::actionbar::NoteAction;
use crate::ui;
use crate::{actionbar::NoteAction, profile::get_display_name};
use egui::Sense;
use enostr::Pubkey;
use nostrdb::{Ndb, Transaction};
@@ -79,12 +79,7 @@ fn mention_ui(
ui.horizontal(|ui| {
let profile = ndb.get_profile_by_pubkey(txn, pk).ok();
let name: String =
if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) {
format!("@{}", name.username())
} else {
"@???".to_string()
};
let name: String = format!("@{}", get_display_name(profile.as_ref()).name());
let resp = ui.add(
egui::Label::new(egui::RichText::new(name).color(link_color).size(size))

View File

@@ -16,6 +16,7 @@ pub use reply_description::reply_desc;
use crate::{
actionbar::NoteAction,
profile::get_display_name,
ui::{self, View},
};
@@ -25,7 +26,7 @@ use enostr::{NoteId, Pubkey};
use nostrdb::{Ndb, Note, NoteKey, Transaction};
use notedeck::{CachedNote, ImageCache, NoteCache, NotedeckTextStyle};
use super::profile::preview::{get_display_name, one_line_display_name_widget};
use super::profile::preview::one_line_display_name_widget;
pub struct NoteView<'a> {
ndb: &'a Ndb,

View File

@@ -0,0 +1,205 @@
use core::f32;
use egui::{vec2, Button, Layout, Margin, RichText, Rounding, ScrollArea, TextEdit};
use notedeck::{ImageCache, NotedeckTextStyle};
use crate::{colors, profile_state::ProfileState};
use super::{banner, unwrap_profile_url, ProfilePic};
pub struct EditProfileView<'a> {
state: &'a mut ProfileState,
img_cache: &'a mut ImageCache,
}
impl<'a> EditProfileView<'a> {
pub fn new(state: &'a mut ProfileState, img_cache: &'a mut ImageCache) -> Self {
Self { state, img_cache }
}
// return true to save
pub fn ui(&mut self, ui: &mut egui::Ui) -> bool {
ScrollArea::vertical()
.show(ui, |ui| {
banner(ui, Some(&self.state.banner), 188.0);
let padding = 24.0;
crate::ui::padding(padding, ui, |ui| {
self.inner(ui, padding);
});
ui.separator();
let mut save = false;
crate::ui::padding(padding, ui, |ui| {
ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| {
if ui
.add(button("Save changes", 119.0).fill(colors::PINK))
.clicked()
{
save = true;
}
});
});
save
})
.inner
}
fn inner(&mut self, ui: &mut egui::Ui, padding: f32) {
ui.spacing_mut().item_spacing = egui::vec2(0.0, 16.0);
let mut pfp_rect = ui.available_rect_before_wrap();
let size = 80.0;
pfp_rect.set_width(size);
pfp_rect.set_height(size);
let pfp_rect = pfp_rect.translate(egui::vec2(0.0, -(padding + 2.0 + (size / 2.0))));
let pfp_url = unwrap_profile_url(if self.state.picture.is_empty() {
None
} else {
Some(&self.state.picture)
});
ui.put(
pfp_rect,
ProfilePic::new(self.img_cache, pfp_url).size(size),
);
in_frame(ui, |ui| {
ui.add(label("Display name"));
ui.add(singleline_textedit(&mut self.state.display_name));
});
in_frame(ui, |ui| {
ui.add(label("Username"));
ui.add(singleline_textedit(&mut self.state.name));
});
in_frame(ui, |ui| {
ui.add(label("Profile picture"));
ui.add(multiline_textedit(&mut self.state.picture));
});
in_frame(ui, |ui| {
ui.add(label("Banner"));
ui.add(multiline_textedit(&mut self.state.banner));
});
in_frame(ui, |ui| {
ui.add(label("About"));
ui.add(multiline_textedit(&mut self.state.about));
});
in_frame(ui, |ui| {
ui.add(label("Website"));
ui.add(singleline_textedit(&mut self.state.website));
});
in_frame(ui, |ui| {
ui.add(label("Lightning network address (lud16)"));
ui.add(multiline_textedit(&mut self.state.lud16));
});
in_frame(ui, |ui| {
ui.add(label("NIP-05 verification"));
ui.add(singleline_textedit(&mut self.state.nip05));
let split = &mut self.state.nip05.split('@');
let prefix = split.next();
let suffix = split.next();
if let Some(prefix) = prefix {
if let Some(suffix) = suffix {
let use_domain = if let Some(f) = prefix.chars().next() {
f == '_'
} else {
false
};
ui.colored_label(
ui.visuals().noninteractive().fg_stroke.color,
RichText::new(if use_domain {
format!("\"{}\" will be used for verification", suffix)
} else {
format!(
"\"{}\" at \"{}\" will be used for verification",
prefix, suffix
)
}),
);
}
}
});
}
}
fn label(text: &str) -> impl egui::Widget + '_ {
move |ui: &mut egui::Ui| -> egui::Response {
ui.label(RichText::new(text).font(NotedeckTextStyle::Body.get_bolded_font(ui.ctx())))
}
}
fn singleline_textedit(data: &mut String) -> impl egui::Widget + '_ {
TextEdit::singleline(data)
.min_size(vec2(0.0, 40.0))
.vertical_align(egui::Align::Center)
.margin(Margin::symmetric(12.0, 10.0))
.desired_width(f32::INFINITY)
}
fn multiline_textedit(data: &mut String) -> impl egui::Widget + '_ {
TextEdit::multiline(data)
// .min_size(vec2(0.0, 40.0))
.vertical_align(egui::Align::TOP)
.margin(Margin::symmetric(12.0, 10.0))
.desired_width(f32::INFINITY)
.desired_rows(1)
}
fn in_frame(ui: &mut egui::Ui, contents: impl FnOnce(&mut egui::Ui)) {
egui::Frame::none().show(ui, |ui| {
ui.spacing_mut().item_spacing = egui::vec2(0.0, 8.0);
contents(ui);
});
}
fn button(text: &str, width: f32) -> egui::Button<'static> {
Button::new(text)
.rounding(Rounding::same(8.0))
.min_size(vec2(width, 40.0))
}
mod preview {
use notedeck::App;
use crate::{
profile_state::ProfileState,
test_data,
ui::{Preview, PreviewConfig},
};
use super::EditProfileView;
pub struct EditProfilePreivew {
state: ProfileState,
}
impl Default for EditProfilePreivew {
fn default() -> Self {
Self {
state: ProfileState::from_profile(&test_data::test_profile_record()),
}
}
}
impl App for EditProfilePreivew {
fn update(&mut self, ctx: &mut notedeck::AppContext<'_>, ui: &mut egui::Ui) {
EditProfileView::new(&mut self.state, ctx.img_cache).ui(ui);
}
}
impl<'a> Preview for EditProfileView<'a> {
type Prev = EditProfilePreivew;
fn preview(_cfg: PreviewConfig) -> Self::Prev {
EditProfilePreivew::default()
}
}
}

View File

@@ -1,11 +1,16 @@
pub mod edit;
pub mod picture;
pub mod preview;
use crate::notes_holder::NotesHolder;
use crate::profile::get_display_name;
use crate::ui::note::NoteOptions;
use egui::{ScrollArea, Widget};
use crate::{colors, images};
use crate::{notes_holder::NotesHolder, NostrName};
pub use edit::EditProfileView;
use egui::load::TexturePoll;
use egui::{vec2, Color32, Label, Layout, Rect, RichText, Rounding, ScrollArea, Sense, Stroke};
use enostr::Pubkey;
use nostrdb::{Ndb, Transaction};
use nostrdb::{Ndb, ProfileRecord, Transaction};
pub use picture::ProfilePic;
pub use preview::ProfilePreview;
use tracing::error;
@@ -13,10 +18,11 @@ use tracing::error;
use crate::{actionbar::NoteAction, notes_holder::NotesHolderStorage, profile::Profile};
use super::timeline::{tabs_ui, TimelineTabView};
use notedeck::{ImageCache, MuteFun, NoteCache};
use notedeck::{Accounts, ImageCache, MuteFun, NoteCache, NotedeckTextStyle};
pub struct ProfileView<'a> {
pubkey: &'a Pubkey,
accounts: &'a Accounts,
col_id: usize,
profiles: &'a mut NotesHolderStorage<Profile>,
note_options: NoteOptions,
@@ -25,9 +31,16 @@ pub struct ProfileView<'a> {
img_cache: &'a mut ImageCache,
}
pub enum ProfileViewAction {
EditProfile,
Note(NoteAction),
}
impl<'a> ProfileView<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
pubkey: &'a Pubkey,
accounts: &'a Accounts,
col_id: usize,
profiles: &'a mut NotesHolderStorage<Profile>,
ndb: &'a Ndb,
@@ -37,6 +50,7 @@ impl<'a> ProfileView<'a> {
) -> Self {
ProfileView {
pubkey,
accounts,
col_id,
profiles,
ndb,
@@ -46,15 +60,18 @@ impl<'a> ProfileView<'a> {
}
}
pub fn ui(&mut self, ui: &mut egui::Ui, is_muted: &MuteFun) -> Option<NoteAction> {
pub fn ui(&mut self, ui: &mut egui::Ui, is_muted: &MuteFun) -> Option<ProfileViewAction> {
let scroll_id = egui::Id::new(("profile_scroll", self.col_id, self.pubkey));
ScrollArea::vertical()
.id_salt(scroll_id)
.show(ui, |ui| {
let mut action = None;
let txn = Transaction::new(self.ndb).expect("txn");
if let Ok(profile) = self.ndb.get_profile_by_pubkey(&txn, self.pubkey.bytes()) {
ProfilePreview::new(&profile, self.img_cache).ui(ui);
if self.profile_body(ui, profile) {
action = Some(ProfileViewAction::EditProfile);
}
}
let profile = self
.profiles
@@ -77,7 +94,7 @@ impl<'a> ProfileView<'a> {
let reversed = false;
TimelineTabView::new(
if let Some(note_action) = TimelineTabView::new(
profile.timeline.current_view(),
reversed,
self.note_options,
@@ -87,7 +104,327 @@ impl<'a> ProfileView<'a> {
self.img_cache,
)
.show(ui)
{
action = Some(ProfileViewAction::Note(note_action));
}
action
})
.inner
}
fn profile_body(&mut self, ui: &mut egui::Ui, profile: ProfileRecord<'_>) -> bool {
let mut action = false;
ui.vertical(|ui| {
banner(
ui,
profile.record().profile().and_then(|p| p.banner()),
120.0,
);
let padding = 12.0;
crate::ui::padding(padding, ui, |ui| {
let mut pfp_rect = ui.available_rect_before_wrap();
let size = 80.0;
pfp_rect.set_width(size);
pfp_rect.set_height(size);
let pfp_rect = pfp_rect.translate(egui::vec2(0.0, -(padding + 2.0 + (size / 2.0))));
ui.horizontal(|ui| {
ui.put(
pfp_rect,
ProfilePic::new(self.img_cache, get_profile_url(Some(&profile))).size(size),
);
if ui.add(copy_key_widget(&pfp_rect)).clicked() {
ui.output_mut(|w| {
w.copied_text = if let Some(bech) = self.pubkey.to_bech() {
bech
} else {
error!("Could not convert Pubkey to bech");
String::new()
}
});
}
if self.accounts.contains_full_kp(self.pubkey) {
ui.with_layout(Layout::right_to_left(egui::Align::Max), |ui| {
if ui.add(edit_profile_button()).clicked() {
action = true;
}
});
}
});
ui.add_space(18.0);
ui.add(display_name_widget(get_display_name(Some(&profile)), false));
ui.add_space(8.0);
ui.add(about_section_widget(&profile));
ui.horizontal_wrapped(|ui| {
if let Some(website_url) = profile
.record()
.profile()
.and_then(|p| p.website())
.filter(|s| !s.is_empty())
{
handle_link(ui, website_url);
}
if let Some(lud16) = profile
.record()
.profile()
.and_then(|p| p.lud16())
.filter(|s| !s.is_empty())
{
handle_lud16(ui, lud16);
}
});
});
});
action
}
}
fn handle_link(ui: &mut egui::Ui, website_url: &str) {
ui.image(egui::include_image!(
"../../../../../assets/icons/links_4x.png"
));
if ui
.label(RichText::new(website_url).color(colors::PINK))
.on_hover_cursor(egui::CursorIcon::PointingHand)
.interact(Sense::click())
.clicked()
{
if let Err(e) = open::that(website_url) {
error!("Failed to open URL {} because: {}", website_url, e);
};
}
}
fn handle_lud16(ui: &mut egui::Ui, lud16: &str) {
ui.image(egui::include_image!(
"../../../../../assets/icons/zap_4x.png"
));
let _ = ui.label(RichText::new(lud16).color(colors::PINK));
}
fn copy_key_widget(pfp_rect: &egui::Rect) -> impl egui::Widget + '_ {
|ui: &mut egui::Ui| -> egui::Response {
let painter = ui.painter();
let copy_key_rect = painter.round_rect_to_pixels(egui::Rect::from_center_size(
pfp_rect.center_bottom(),
egui::vec2(48.0, 28.0),
));
let resp = ui.interact(
copy_key_rect,
ui.id().with("custom_painter"),
Sense::click(),
);
let copy_key_rounding = Rounding::same(100.0);
let fill_color = if resp.hovered() {
ui.visuals().widgets.inactive.weak_bg_fill
} else {
ui.visuals().noninteractive().bg_stroke.color
};
painter.rect_filled(copy_key_rect, copy_key_rounding, fill_color);
let stroke_color = ui.visuals().widgets.inactive.weak_bg_fill;
painter.rect_stroke(
copy_key_rect.shrink(1.0),
copy_key_rounding,
Stroke::new(1.0, stroke_color),
);
egui::Image::new(egui::include_image!(
"../../../../../assets/icons/key_4x.png"
))
.paint_at(
ui,
painter.round_rect_to_pixels(egui::Rect::from_center_size(
copy_key_rect.center(),
egui::vec2(16.0, 16.0),
)),
);
resp
}
}
fn edit_profile_button() -> impl egui::Widget + 'static {
|ui: &mut egui::Ui| -> egui::Response {
let (rect, resp) = ui.allocate_exact_size(vec2(124.0, 32.0), Sense::click());
let painter = ui.painter_at(rect);
let rect = painter.round_rect_to_pixels(rect);
painter.rect_filled(
rect,
Rounding::same(8.0),
if resp.hovered() {
ui.visuals().widgets.active.bg_fill
} else {
ui.visuals().widgets.inactive.bg_fill
},
);
painter.rect_stroke(
rect.shrink(1.0),
Rounding::same(8.0),
if resp.hovered() {
ui.visuals().widgets.active.bg_stroke
} else {
ui.visuals().widgets.inactive.bg_stroke
},
);
let edit_icon_size = vec2(16.0, 16.0);
let galley = painter.layout(
"Edit Profile".to_owned(),
NotedeckTextStyle::Button.get_font_id(ui.ctx()),
ui.visuals().text_color(),
rect.width(),
);
let space_between_icon_galley = 8.0;
let half_icon_size = edit_icon_size.x / 2.0;
let galley_rect = {
let galley_rect = Rect::from_center_size(rect.center(), galley.rect.size());
galley_rect.translate(vec2(half_icon_size + space_between_icon_galley / 2.0, 0.0))
};
let edit_icon_rect = {
let mut center = galley_rect.left_center();
center.x -= half_icon_size + space_between_icon_galley;
painter.round_rect_to_pixels(Rect::from_center_size(
painter.round_pos_to_pixel_center(center),
edit_icon_size,
))
};
painter.galley(galley_rect.left_top(), galley, Color32::WHITE);
egui::Image::new(egui::include_image!(
"../../../../../assets/icons/edit_icon_4x_dark.png"
))
.paint_at(ui, edit_icon_rect);
resp
}
}
fn display_name_widget(name: NostrName<'_>, add_placeholder_space: bool) -> impl egui::Widget + '_ {
move |ui: &mut egui::Ui| -> egui::Response {
let disp_resp = name.display_name.map(|disp_name| {
ui.add(
Label::new(
RichText::new(disp_name).text_style(NotedeckTextStyle::Heading3.text_style()),
)
.selectable(false),
)
});
let (username_resp, nip05_resp) = ui
.horizontal(|ui| {
let username_resp = name.username.map(|username| {
ui.add(
Label::new(
RichText::new(format!("@{}", username))
.size(16.0)
.color(colors::MID_GRAY),
)
.selectable(false),
)
});
let nip05_resp = name.nip05.map(|nip05| {
ui.image(egui::include_image!(
"../../../../../assets/icons/verified_4x.png"
));
ui.add(Label::new(
RichText::new(nip05).size(16.0).color(colors::TEAL),
))
});
(username_resp, nip05_resp)
})
.inner;
let resp = match (disp_resp, username_resp, nip05_resp) {
(Some(disp), Some(username), Some(nip05)) => disp.union(username).union(nip05),
(Some(disp), Some(username), None) => disp.union(username),
(Some(disp), None, None) => disp,
(None, Some(username), Some(nip05)) => username.union(nip05),
(None, Some(username), None) => username,
_ => ui.add(Label::new(RichText::new(name.name()))),
};
if add_placeholder_space {
ui.add_space(16.0);
}
resp
}
}
pub fn get_profile_url<'a>(profile: Option<&ProfileRecord<'a>>) -> &'a str {
unwrap_profile_url(profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())))
}
pub fn unwrap_profile_url(maybe_url: Option<&str>) -> &str {
if let Some(url) = maybe_url {
url
} else {
ProfilePic::no_pfp_url()
}
}
fn about_section_widget<'a, 'b>(profile: &'b ProfileRecord<'a>) -> impl egui::Widget + 'b
where
'b: 'a,
{
move |ui: &mut egui::Ui| {
if let Some(about) = profile.record().profile().and_then(|p| p.about()) {
let resp = ui.label(about);
ui.add_space(8.0);
resp
} else {
// need any Response so we dont need an Option
ui.allocate_response(egui::Vec2::ZERO, egui::Sense::hover())
}
}
}
fn banner_texture(ui: &mut egui::Ui, banner_url: &str) -> Option<egui::load::SizedTexture> {
// TODO: cache banner
if !banner_url.is_empty() {
let texture_load_res =
egui::Image::new(banner_url).load_for_size(ui.ctx(), ui.available_size());
if let Ok(texture_poll) = texture_load_res {
match texture_poll {
TexturePoll::Pending { .. } => {}
TexturePoll::Ready { texture, .. } => return Some(texture),
}
}
}
None
}
fn banner(ui: &mut egui::Ui, banner_url: Option<&str>, height: f32) -> egui::Response {
ui.add_sized([ui.available_size().x, height], |ui: &mut egui::Ui| {
banner_url
.and_then(|url| banner_texture(ui, url))
.map(|texture| {
images::aspect_fill(
ui,
Sense::hover(),
texture.id,
texture.size.x / texture.size.y,
)
})
.unwrap_or_else(|| ui.label(""))
})
}

View File

@@ -1,13 +1,13 @@
use crate::ui::ProfilePic;
use crate::{colors, images, DisplayName};
use egui::load::TexturePoll;
use egui::{Frame, Label, RichText, Sense, Widget};
use crate::NostrName;
use egui::{Frame, Label, RichText, Widget};
use egui_extras::Size;
use enostr::{NoteId, Pubkey};
use nostrdb::{Ndb, ProfileRecord, Transaction};
use nostrdb::ProfileRecord;
use notedeck::{ImageCache, NotedeckTextStyle, UserAccount};
use super::{about_section_widget, banner, display_name_widget, get_display_name, get_profile_url};
pub struct ProfilePreview<'a, 'cache> {
profile: &'a ProfileRecord<'a>,
cache: &'cache mut ImageCache,
@@ -28,41 +28,6 @@ impl<'a, 'cache> ProfilePreview<'a, 'cache> {
self.banner_height = size;
}
fn banner_texture(
ui: &mut egui::Ui,
profile: &ProfileRecord<'_>,
) -> Option<egui::load::SizedTexture> {
// TODO: cache banner
let banner = profile.record().profile().and_then(|p| p.banner());
if let Some(banner) = banner {
let texture_load_res =
egui::Image::new(banner).load_for_size(ui.ctx(), ui.available_size());
if let Ok(texture_poll) = texture_load_res {
match texture_poll {
TexturePoll::Pending { .. } => {}
TexturePoll::Ready { texture, .. } => return Some(texture),
}
}
}
None
}
fn banner(ui: &mut egui::Ui, profile: &ProfileRecord<'_>) -> egui::Response {
if let Some(texture) = Self::banner_texture(ui, profile) {
images::aspect_fill(
ui,
Sense::hover(),
texture.id,
texture.size.x / texture.size.y,
)
} else {
// TODO: default banner texture
ui.label("")
}
}
fn body(self, ui: &mut egui::Ui) {
let padding = 12.0;
crate::ui::padding(padding, ui, |ui| {
@@ -88,9 +53,11 @@ impl<'a, 'cache> ProfilePreview<'a, 'cache> {
impl egui::Widget for ProfilePreview<'_, '_> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
ui.vertical(|ui| {
ui.add_sized([ui.available_size().x, 80.0], |ui: &mut egui::Ui| {
ProfilePreview::banner(ui, self.profile)
});
banner(
ui,
self.profile.record().profile().and_then(|p| p.banner()),
80.0,
);
self.body(ui);
})
@@ -183,22 +150,6 @@ mod previews {
}
}
pub fn get_display_name<'a>(profile: Option<&ProfileRecord<'a>>) -> DisplayName<'a> {
if let Some(name) = profile.and_then(|p| crate::profile::get_profile_name(p)) {
name
} else {
DisplayName::One("??")
}
}
pub fn get_profile_url<'a>(profile: Option<&ProfileRecord<'a>>) -> &'a str {
if let Some(url) = profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())) {
url
} else {
ProfilePic::no_pfp_url()
}
}
pub fn get_profile_url_owned(profile: Option<ProfileRecord<'_>>) -> &str {
if let Some(url) = profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())) {
url
@@ -223,106 +174,19 @@ pub fn get_account_url<'a>(
}
}
fn display_name_widget(
display_name: DisplayName<'_>,
add_placeholder_space: bool,
) -> impl egui::Widget + '_ {
move |ui: &mut egui::Ui| match display_name {
DisplayName::One(n) => {
let name_response = ui.add(
Label::new(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style()))
.selectable(false),
);
if add_placeholder_space {
ui.add_space(16.0);
}
name_response
}
DisplayName::Both {
display_name,
username,
} => {
ui.add(
Label::new(
RichText::new(display_name)
.text_style(NotedeckTextStyle::Heading3.text_style()),
)
.selectable(false),
);
ui.add(
Label::new(
RichText::new(format!("@{}", username))
.size(12.0)
.color(colors::MID_GRAY),
)
.selectable(false),
)
}
}
}
pub fn one_line_display_name_widget<'a>(
visuals: &egui::Visuals,
display_name: DisplayName<'a>,
display_name: NostrName<'a>,
style: NotedeckTextStyle,
) -> impl egui::Widget + 'a {
let text_style = style.text_style();
let color = visuals.noninteractive().fg_stroke.color;
move |ui: &mut egui::Ui| match display_name {
DisplayName::One(n) => ui.label(RichText::new(n).text_style(text_style).color(color)),
DisplayName::Both {
display_name,
username: _,
} => ui.label(
RichText::new(display_name)
move |ui: &mut egui::Ui| -> egui::Response {
ui.label(
RichText::new(display_name.name())
.text_style(text_style)
.color(color),
),
)
}
}
fn about_section_widget<'a, 'b>(profile: &'b ProfileRecord<'a>) -> impl egui::Widget + 'b
where
'b: 'a,
{
move |ui: &mut egui::Ui| {
if let Some(about) = profile.record().profile().and_then(|p| p.about()) {
ui.label(about)
} else {
// need any Response so we dont need an Option
ui.allocate_response(egui::Vec2::ZERO, egui::Sense::hover())
}
}
}
fn get_display_name_as_string<'a>(profile: Option<&ProfileRecord<'a>>) -> &'a str {
let display_name = get_display_name(profile);
match display_name {
DisplayName::One(n) => n,
DisplayName::Both { display_name, .. } => display_name,
}
}
pub fn get_profile_displayname_string<'a>(txn: &'a Transaction, ndb: &Ndb, pk: &Pubkey) -> &'a str {
let profile = ndb.get_profile_by_pubkey(txn, pk.bytes()).ok();
get_display_name_as_string(profile.as_ref())
}
pub fn get_note_users_displayname_string<'a>(
txn: &'a Transaction,
ndb: &Ndb,
id: &NoteId,
) -> &'a str {
let note = ndb.get_note_by_id(txn, id.bytes());
let profile = if let Ok(note) = note {
ndb.get_profile_by_pubkey(txn, note.pubkey()).ok()
} else {
None
};
get_display_name_as_string(profile.as_ref())
}