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