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