notedeck

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

edit.rs (8385B)


      1 use core::f32;
      2 
      3 use egui::{vec2, Button, CornerRadius, Layout, Margin, RichText, ScrollArea, TextEdit};
      4 use egui_winit::clipboard::Clipboard;
      5 use enostr::ProfileState;
      6 use notedeck::DragResponse;
      7 use notedeck::{
      8     profile::unwrap_profile_url, tr, Images, Localization, MediaJobSender, NotedeckTextStyle,
      9 };
     10 use notedeck_ui::context_menu::{input_context, PasteBehavior};
     11 use notedeck_ui::{profile::banner, ProfilePic};
     12 
     13 pub struct EditProfileView<'a> {
     14     state: &'a mut ProfileState,
     15     clipboard: &'a mut Clipboard,
     16     img_cache: &'a mut Images,
     17     i18n: &'a mut Localization,
     18     jobs: &'a MediaJobSender,
     19 }
     20 
     21 impl<'a> EditProfileView<'a> {
     22     pub fn new(
     23         i18n: &'a mut Localization,
     24         state: &'a mut ProfileState,
     25         img_cache: &'a mut Images,
     26         clipboard: &'a mut Clipboard,
     27         jobs: &'a MediaJobSender,
     28     ) -> Self {
     29         Self {
     30             i18n,
     31             state,
     32             img_cache,
     33             clipboard,
     34             jobs,
     35         }
     36     }
     37 
     38     pub fn scroll_id() -> egui::Id {
     39         egui::Id::new("edit_profile")
     40     }
     41 
     42     // return true to save
     43     pub fn ui(&mut self, ui: &mut egui::Ui) -> DragResponse<bool> {
     44         let scroll_out = ScrollArea::vertical()
     45             .id_salt(EditProfileView::scroll_id())
     46             .stick_to_bottom(true)
     47             .show(ui, |ui| {
     48                 banner(ui, self.state.banner(), 188.0);
     49 
     50                 let padding = 24.0;
     51                 notedeck_ui::padding(padding, ui, |ui| {
     52                     self.inner(ui, padding);
     53                 });
     54 
     55                 ui.separator();
     56 
     57                 let mut save = false;
     58                 notedeck_ui::padding(padding, ui, |ui| {
     59                     ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| {
     60                         if ui
     61                             .add(
     62                                 button(
     63                                     tr!(
     64                                         self.i18n,
     65                                         "Save changes",
     66                                         "Button label to save profile changes"
     67                                     )
     68                                     .as_str(),
     69                                     119.0,
     70                                 )
     71                                 .fill(notedeck_ui::colors::PINK),
     72                             )
     73                             .clicked()
     74                         {
     75                             save = true;
     76                         }
     77                     });
     78                 });
     79 
     80                 Some(save)
     81             });
     82         DragResponse::scroll(scroll_out)
     83     }
     84 
     85     fn inner(&mut self, ui: &mut egui::Ui, padding: f32) {
     86         ui.spacing_mut().item_spacing = egui::vec2(0.0, 16.0);
     87         let mut pfp_rect = ui.available_rect_before_wrap();
     88         let size = 80.0;
     89         pfp_rect.set_width(size);
     90         pfp_rect.set_height(size);
     91         let pfp_rect = pfp_rect.translate(egui::vec2(0.0, -(padding + 2.0 + (size / 2.0))));
     92 
     93         let pfp_url = unwrap_profile_url(self.state.picture());
     94         ui.put(
     95             pfp_rect,
     96             &mut ProfilePic::new(self.img_cache, self.jobs, pfp_url)
     97                 .size(size)
     98                 .border(ProfilePic::border_stroke(ui)),
     99         );
    100 
    101         in_frame(ui, |ui| {
    102             ui.add(label(
    103                 tr!(
    104                     self.i18n,
    105                     "Display name",
    106                     "Profile display name field label"
    107                 )
    108                 .as_str(),
    109             ));
    110             singleline_textedit(ui, self.state.str_mut("display_name"), self.clipboard);
    111         });
    112 
    113         in_frame(ui, |ui| {
    114             ui.add(label(
    115                 tr!(self.i18n, "Username", "Profile username field label").as_str(),
    116             ));
    117             singleline_textedit(ui, self.state.str_mut("name"), self.clipboard);
    118         });
    119 
    120         in_frame(ui, |ui| {
    121             ui.add(label(
    122                 tr!(
    123                     self.i18n,
    124                     "Profile picture",
    125                     "Profile picture URL field label"
    126                 )
    127                 .as_str(),
    128             ));
    129             multiline_textedit(ui, self.state.str_mut("picture"), self.clipboard);
    130         });
    131 
    132         in_frame(ui, |ui| {
    133             ui.add(label(
    134                 tr!(self.i18n, "Banner", "Profile banner URL field label").as_str(),
    135             ));
    136             multiline_textedit(ui, self.state.str_mut("banner"), self.clipboard);
    137         });
    138 
    139         in_frame(ui, |ui| {
    140             ui.add(label(
    141                 tr!(self.i18n, "About", "Profile about/bio field label").as_str(),
    142             ));
    143             multiline_textedit(ui, self.state.str_mut("about"), self.clipboard);
    144         });
    145 
    146         in_frame(ui, |ui| {
    147             ui.add(label(
    148                 tr!(self.i18n, "Website", "Profile website field label").as_str(),
    149             ));
    150             singleline_textedit(ui, self.state.str_mut("website"), self.clipboard);
    151         });
    152 
    153         in_frame(ui, |ui| {
    154             ui.add(label(
    155                 tr!(
    156                     self.i18n,
    157                     "Lightning network address (lud16)",
    158                     "Bitcoin Lightning network address field label"
    159                 )
    160                 .as_str(),
    161             ));
    162             multiline_textedit(ui, self.state.str_mut("lud16"), self.clipboard);
    163         });
    164 
    165         in_frame(ui, |ui| {
    166             ui.add(label(
    167                 tr!(
    168                     self.i18n,
    169                     "Nostr address (NIP-05 identity)",
    170                     "NIP-05 identity field label"
    171                 )
    172                 .as_str(),
    173             ));
    174 
    175             singleline_textedit(ui, self.state.str_mut("nip05"), self.clipboard);
    176 
    177             let Some(nip05) = self.state.nip05() else {
    178                 return;
    179             };
    180 
    181             let mut split = nip05.split('@');
    182 
    183             let Some(prefix) = split.next() else {
    184                 return;
    185             };
    186             let Some(suffix) = split.next() else {
    187                 return;
    188             };
    189 
    190             let use_domain = if let Some(f) = prefix.chars().next() {
    191                 f == '_'
    192             } else {
    193                 false
    194             };
    195             ui.colored_label(
    196                 ui.visuals().noninteractive().fg_stroke.color,
    197                 RichText::new(if use_domain {
    198                     tr!(
    199                         self.i18n,
    200                         "\"{domain}\" will be used for identification",
    201                         "Domain identification message",
    202                         domain = suffix
    203                     )
    204                 } else {
    205                     tr!(
    206                         self.i18n,
    207                         "\"{username}\" at \"{domain}\" will be used for identification",
    208                         "Username and domain identification message",
    209                         username = prefix,
    210                         domain = suffix
    211                     )
    212                 }),
    213             );
    214         });
    215     }
    216 }
    217 
    218 fn label(text: &str) -> impl egui::Widget + '_ {
    219     move |ui: &mut egui::Ui| -> egui::Response {
    220         ui.label(RichText::new(text).font(NotedeckTextStyle::Body.get_bolded_font(ui.ctx())))
    221     }
    222 }
    223 
    224 fn singleline_textedit(ui: &mut egui::Ui, data: &mut String, clipboard: &mut Clipboard) {
    225     let r = ui.add(
    226         TextEdit::singleline(data)
    227             .min_size(vec2(0.0, 40.0))
    228             .vertical_align(egui::Align::Center)
    229             .margin(Margin::symmetric(12, 10))
    230             .desired_width(f32::INFINITY),
    231     );
    232 
    233     input_context(ui, &r, clipboard, data, PasteBehavior::Clear);
    234 }
    235 
    236 fn multiline_textedit(ui: &mut egui::Ui, data: &mut String, clipboard: &mut Clipboard) {
    237     let r = ui.add(
    238         TextEdit::multiline(data)
    239             // .min_size(vec2(0.0, 40.0))
    240             .vertical_align(egui::Align::TOP)
    241             .margin(Margin::symmetric(12, 10))
    242             .desired_width(f32::INFINITY)
    243             .desired_rows(1),
    244     );
    245 
    246     input_context(ui, &r, clipboard, data, PasteBehavior::Clear);
    247 }
    248 
    249 fn in_frame(ui: &mut egui::Ui, contents: impl FnOnce(&mut egui::Ui)) {
    250     egui::Frame::new().show(ui, |ui| {
    251         ui.spacing_mut().item_spacing = egui::vec2(0.0, 8.0);
    252         contents(ui);
    253     });
    254 }
    255 
    256 fn button(text: &str, width: f32) -> egui::Button<'static> {
    257     Button::new(text)
    258         .corner_radius(CornerRadius::same(8))
    259         .min_size(vec2(width, 40.0))
    260 }