mod.rs (4660B)
1 use nostrdb::ProfileRecord; 2 3 pub mod name; 4 pub mod picture; 5 pub mod preview; 6 7 pub use picture::ProfilePic; 8 pub use preview::ProfilePreview; 9 10 use egui::{load::TexturePoll, Label, RichText}; 11 use notedeck::{IsFollowing, NostrName, NotedeckTextStyle}; 12 13 use crate::{app_images, colors, widgets::styled_button_toggleable}; 14 15 pub fn display_name_widget<'a>( 16 name: &'a NostrName<'a>, 17 add_placeholder_space: bool, 18 ) -> impl egui::Widget + 'a { 19 move |ui: &mut egui::Ui| -> egui::Response { 20 let disp_resp = name.display_name.map(|disp_name| { 21 ui.add( 22 Label::new( 23 RichText::new(disp_name).text_style(NotedeckTextStyle::Heading3.text_style()), 24 ) 25 .selectable(false), 26 ) 27 }); 28 29 let (username_resp, nip05_resp) = ui 30 .horizontal_wrapped(|ui| { 31 let username_resp = name.username.map(|username| { 32 ui.add( 33 Label::new( 34 RichText::new(format!("@{username}")) 35 .size(16.0) 36 .color(crate::colors::MID_GRAY), 37 ) 38 .selectable(false), 39 ) 40 }); 41 42 if name.username.is_some() && name.nip05.is_some() { 43 ui.end_row(); 44 } 45 46 let nip05_resp = name.nip05.map(|nip05| { 47 ui.horizontal(|ui| { 48 ui.spacing_mut().item_spacing.x = 2.0; 49 50 ui.add(app_images::verified_image()); 51 52 ui.label(RichText::new(nip05).size(16.0).color(crate::colors::TEAL)) 53 .on_hover_text(nip05) 54 }) 55 .inner 56 }); 57 58 (username_resp, nip05_resp) 59 }) 60 .inner; 61 62 let resp = match (disp_resp, username_resp, nip05_resp) { 63 (Some(disp), Some(username), Some(nip05)) => disp.union(username).union(nip05), 64 (Some(disp), Some(username), None) => disp.union(username), 65 (Some(disp), None, None) => disp, 66 (None, Some(username), Some(nip05)) => username.union(nip05), 67 (None, Some(username), None) => username, 68 _ => ui.add(Label::new(RichText::new(name.name()))), 69 }; 70 71 if add_placeholder_space { 72 ui.add_space(16.0); 73 } 74 75 resp 76 } 77 } 78 79 pub fn about_section_widget<'a>(profile: Option<&'a ProfileRecord<'a>>) -> impl egui::Widget + 'a { 80 move |ui: &mut egui::Ui| { 81 if let Some(about) = profile 82 .map(|p| p.record().profile()) 83 .and_then(|p| p.and_then(|p| p.about())) 84 { 85 let resp = ui.label(about); 86 ui.add_space(8.0); 87 resp 88 } else { 89 // need any Response so we dont need an Option 90 ui.allocate_response(egui::Vec2::ZERO, egui::Sense::hover()) 91 } 92 } 93 } 94 95 pub fn banner_texture(ui: &mut egui::Ui, banner_url: &str) -> Option<egui::load::SizedTexture> { 96 // TODO: cache banner 97 if !banner_url.is_empty() { 98 let texture_load_res = 99 egui::Image::new(banner_url).load_for_size(ui.ctx(), ui.available_size()); 100 if let Ok(texture_poll) = texture_load_res { 101 match texture_poll { 102 TexturePoll::Pending { .. } => {} 103 TexturePoll::Ready { texture, .. } => return Some(texture), 104 } 105 } 106 } 107 108 None 109 } 110 111 pub fn banner(ui: &mut egui::Ui, banner_url: Option<&str>, height: f32) -> egui::Response { 112 ui.add_sized([ui.available_size().x, height], |ui: &mut egui::Ui| { 113 banner_url 114 .and_then(|url| banner_texture(ui, url)) 115 .map(|texture| { 116 crate::images::aspect_fill( 117 ui, 118 egui::Sense::hover(), 119 texture.id, 120 texture.size.x / texture.size.y, 121 ) 122 }) 123 .unwrap_or_else(|| ui.label("")) 124 }) 125 } 126 127 pub fn follow_button(following: IsFollowing) -> impl egui::Widget + 'static { 128 move |ui: &mut egui::Ui| -> egui::Response { 129 let (bg_color, text) = match following { 130 IsFollowing::Unknown => (ui.visuals().noninteractive().bg_fill, "Unknown"), 131 IsFollowing::Yes => (ui.visuals().widgets.inactive.bg_fill, "Unfollow"), 132 IsFollowing::No => (colors::PINK, "Follow"), 133 }; 134 135 let enabled = following != IsFollowing::Unknown; 136 ui.add(styled_button_toggleable(text, bg_color, enabled)) 137 } 138 }