notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

preview.rs (9560B)


      1 use crate::app_style::{get_font_size, NotedeckTextStyle};
      2 use crate::imgcache::ImageCache;
      3 use crate::storage::{DataPath, DataPathType};
      4 use crate::ui::ProfilePic;
      5 use crate::user_account::UserAccount;
      6 use crate::{colors, images, DisplayName};
      7 use egui::load::TexturePoll;
      8 use egui::{Frame, Label, RichText, Sense, Widget};
      9 use egui_extras::Size;
     10 use enostr::{NoteId, Pubkey};
     11 use nostrdb::{Ndb, ProfileRecord, Transaction};
     12 
     13 pub struct ProfilePreview<'a, 'cache> {
     14     profile: &'a ProfileRecord<'a>,
     15     cache: &'cache mut ImageCache,
     16     banner_height: Size,
     17 }
     18 
     19 impl<'a, 'cache> ProfilePreview<'a, 'cache> {
     20     pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut ImageCache) -> Self {
     21         let banner_height = Size::exact(80.0);
     22         ProfilePreview {
     23             profile,
     24             cache,
     25             banner_height,
     26         }
     27     }
     28 
     29     pub fn banner_height(&mut self, size: Size) {
     30         self.banner_height = size;
     31     }
     32 
     33     fn banner_texture(
     34         ui: &mut egui::Ui,
     35         profile: &ProfileRecord<'_>,
     36     ) -> Option<egui::load::SizedTexture> {
     37         // TODO: cache banner
     38         let banner = profile.record().profile().and_then(|p| p.banner());
     39 
     40         if let Some(banner) = banner {
     41             let texture_load_res =
     42                 egui::Image::new(banner).load_for_size(ui.ctx(), ui.available_size());
     43             if let Ok(texture_poll) = texture_load_res {
     44                 match texture_poll {
     45                     TexturePoll::Pending { .. } => {}
     46                     TexturePoll::Ready { texture, .. } => return Some(texture),
     47                 }
     48             }
     49         }
     50 
     51         None
     52     }
     53 
     54     fn banner(ui: &mut egui::Ui, profile: &ProfileRecord<'_>) -> egui::Response {
     55         if let Some(texture) = Self::banner_texture(ui, profile) {
     56             images::aspect_fill(
     57                 ui,
     58                 Sense::hover(),
     59                 texture.id,
     60                 texture.size.x / texture.size.y,
     61             )
     62         } else {
     63             // TODO: default banner texture
     64             ui.label("")
     65         }
     66     }
     67 
     68     fn body(self, ui: &mut egui::Ui) {
     69         crate::ui::padding(12.0, ui, |ui| {
     70             ui.add(ProfilePic::new(self.cache, get_profile_url(Some(self.profile))).size(80.0));
     71             ui.add(display_name_widget(
     72                 get_display_name(Some(self.profile)),
     73                 false,
     74             ));
     75             ui.add(about_section_widget(self.profile));
     76         });
     77     }
     78 }
     79 
     80 impl egui::Widget for ProfilePreview<'_, '_> {
     81     fn ui(self, ui: &mut egui::Ui) -> egui::Response {
     82         ui.vertical(|ui| {
     83             ui.add_sized([ui.available_size().x, 80.0], |ui: &mut egui::Ui| {
     84                 ProfilePreview::banner(ui, self.profile)
     85             });
     86 
     87             self.body(ui);
     88         })
     89         .response
     90     }
     91 }
     92 
     93 pub struct SimpleProfilePreview<'a, 'cache> {
     94     profile: Option<&'a ProfileRecord<'a>>,
     95     cache: &'cache mut ImageCache,
     96     is_nsec: bool,
     97 }
     98 
     99 impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> {
    100     pub fn new(
    101         profile: Option<&'a ProfileRecord<'a>>,
    102         cache: &'cache mut ImageCache,
    103         is_nsec: bool,
    104     ) -> Self {
    105         SimpleProfilePreview {
    106             profile,
    107             cache,
    108             is_nsec,
    109         }
    110     }
    111 }
    112 
    113 impl egui::Widget for SimpleProfilePreview<'_, '_> {
    114     fn ui(self, ui: &mut egui::Ui) -> egui::Response {
    115         Frame::none()
    116             .show(ui, |ui| {
    117                 ui.add(ProfilePic::new(self.cache, get_profile_url(self.profile)).size(48.0));
    118                 ui.vertical(|ui| {
    119                     ui.add(display_name_widget(get_display_name(self.profile), true));
    120                     if !self.is_nsec {
    121                         ui.add(
    122                             Label::new(
    123                                 RichText::new("Read only")
    124                                     .size(get_font_size(ui.ctx(), &NotedeckTextStyle::Tiny))
    125                                     .color(ui.visuals().warn_fg_color),
    126                             )
    127                             .selectable(false),
    128                         );
    129                     }
    130                 });
    131             })
    132             .response
    133     }
    134 }
    135 
    136 mod previews {
    137     use super::*;
    138     use crate::test_data::test_profile_record;
    139     use crate::ui::{Preview, PreviewConfig, View};
    140 
    141     pub struct ProfilePreviewPreview<'a> {
    142         profile: ProfileRecord<'a>,
    143         cache: ImageCache,
    144     }
    145 
    146     impl ProfilePreviewPreview<'_> {
    147         pub fn new() -> Self {
    148             let profile = test_profile_record();
    149             let path = DataPath::new("previews")
    150                 .path(DataPathType::Cache)
    151                 .join(ImageCache::rel_dir());
    152             let cache = ImageCache::new(path);
    153             ProfilePreviewPreview { profile, cache }
    154         }
    155     }
    156 
    157     impl Default for ProfilePreviewPreview<'_> {
    158         fn default() -> Self {
    159             ProfilePreviewPreview::new()
    160         }
    161     }
    162 
    163     impl View for ProfilePreviewPreview<'_> {
    164         fn ui(&mut self, ui: &mut egui::Ui) {
    165             ProfilePreview::new(&self.profile, &mut self.cache).ui(ui);
    166         }
    167     }
    168 
    169     impl<'a> Preview for ProfilePreview<'a, '_> {
    170         /// A preview of the profile preview :D
    171         type Prev = ProfilePreviewPreview<'a>;
    172 
    173         fn preview(_cfg: PreviewConfig) -> Self::Prev {
    174             ProfilePreviewPreview::new()
    175         }
    176     }
    177 }
    178 
    179 pub fn get_display_name<'a>(profile: Option<&ProfileRecord<'a>>) -> DisplayName<'a> {
    180     if let Some(name) = profile.and_then(|p| crate::profile::get_profile_name(p)) {
    181         name
    182     } else {
    183         DisplayName::One("??")
    184     }
    185 }
    186 
    187 pub fn get_profile_url<'a>(profile: Option<&ProfileRecord<'a>>) -> &'a str {
    188     if let Some(url) = profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())) {
    189         url
    190     } else {
    191         ProfilePic::no_pfp_url()
    192     }
    193 }
    194 
    195 pub fn get_profile_url_owned(profile: Option<ProfileRecord<'_>>) -> &str {
    196     if let Some(url) = profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())) {
    197         url
    198     } else {
    199         ProfilePic::no_pfp_url()
    200     }
    201 }
    202 
    203 pub fn get_account_url<'a>(
    204     txn: &'a nostrdb::Transaction,
    205     ndb: &nostrdb::Ndb,
    206     account: Option<&UserAccount>,
    207 ) -> &'a str {
    208     if let Some(selected_account) = account {
    209         if let Ok(profile) = ndb.get_profile_by_pubkey(txn, selected_account.pubkey.bytes()) {
    210             get_profile_url_owned(Some(profile))
    211         } else {
    212             get_profile_url_owned(None)
    213         }
    214     } else {
    215         get_profile_url(None)
    216     }
    217 }
    218 
    219 fn display_name_widget(
    220     display_name: DisplayName<'_>,
    221     add_placeholder_space: bool,
    222 ) -> impl egui::Widget + '_ {
    223     move |ui: &mut egui::Ui| match display_name {
    224         DisplayName::One(n) => {
    225             let name_response = ui.add(
    226                 Label::new(RichText::new(n).text_style(NotedeckTextStyle::Heading3.text_style()))
    227                     .selectable(false),
    228             );
    229             if add_placeholder_space {
    230                 ui.add_space(16.0);
    231             }
    232             name_response
    233         }
    234 
    235         DisplayName::Both {
    236             display_name,
    237             username,
    238         } => {
    239             ui.add(
    240                 Label::new(
    241                     RichText::new(display_name)
    242                         .text_style(NotedeckTextStyle::Heading3.text_style()),
    243                 )
    244                 .selectable(false),
    245             );
    246 
    247             ui.add(
    248                 Label::new(
    249                     RichText::new(format!("@{}", username))
    250                         .size(12.0)
    251                         .color(colors::MID_GRAY),
    252                 )
    253                 .selectable(false),
    254             )
    255         }
    256     }
    257 }
    258 
    259 pub fn one_line_display_name_widget(
    260     display_name: DisplayName<'_>,
    261     style: NotedeckTextStyle,
    262 ) -> impl egui::Widget + '_ {
    263     let text_style = style.text_style();
    264     move |ui: &mut egui::Ui| match display_name {
    265         DisplayName::One(n) => ui.label(
    266             RichText::new(n)
    267                 .text_style(text_style)
    268                 .color(colors::GRAY_SECONDARY),
    269         ),
    270 
    271         DisplayName::Both {
    272             display_name,
    273             username: _,
    274         } => ui.label(
    275             RichText::new(display_name)
    276                 .text_style(text_style)
    277                 .color(colors::GRAY_SECONDARY),
    278         ),
    279     }
    280 }
    281 
    282 fn about_section_widget<'a, 'b>(profile: &'b ProfileRecord<'a>) -> impl egui::Widget + 'b
    283 where
    284     'b: 'a,
    285 {
    286     move |ui: &mut egui::Ui| {
    287         if let Some(about) = profile.record().profile().and_then(|p| p.about()) {
    288             ui.label(about)
    289         } else {
    290             // need any Response so we dont need an Option
    291             ui.allocate_response(egui::Vec2::ZERO, egui::Sense::hover())
    292         }
    293     }
    294 }
    295 
    296 fn get_display_name_as_string<'a>(profile: Option<&ProfileRecord<'a>>) -> &'a str {
    297     let display_name = get_display_name(profile);
    298     match display_name {
    299         DisplayName::One(n) => n,
    300         DisplayName::Both { display_name, .. } => display_name,
    301     }
    302 }
    303 
    304 pub fn get_profile_displayname_string<'a>(txn: &'a Transaction, ndb: &Ndb, pk: &Pubkey) -> &'a str {
    305     let profile = ndb.get_profile_by_pubkey(txn, pk.bytes()).ok();
    306     get_display_name_as_string(profile.as_ref())
    307 }
    308 
    309 pub fn get_note_users_displayname_string<'a>(
    310     txn: &'a Transaction,
    311     ndb: &Ndb,
    312     id: &NoteId,
    313 ) -> &'a str {
    314     let note = ndb.get_note_by_id(txn, id.bytes());
    315     let profile = if let Ok(note) = note {
    316         ndb.get_profile_by_pubkey(txn, note.pubkey()).ok()
    317     } else {
    318         None
    319     };
    320 
    321     get_display_name_as_string(profile.as_ref())
    322 }