@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
205
crates/notedeck_columns/src/ui/profile/edit.rs
Normal file
205
crates/notedeck_columns/src/ui/profile/edit.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(""))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user