relay.rs (11118B)
1 use std::collections::HashMap; 2 3 use crate::colors::PINK; 4 use crate::relay_pool_manager::{RelayPoolManager, RelayStatus}; 5 use crate::ui::{Preview, PreviewConfig, View}; 6 use egui::{Align, Button, Frame, Id, Image, Layout, Margin, Rgba, RichText, Rounding, Ui, Vec2}; 7 8 use enostr::RelayPool; 9 use notedeck::{Accounts, NotedeckTextStyle}; 10 11 use tracing::debug; 12 13 use super::add_column::sized_button; 14 use super::padding; 15 16 pub struct RelayView<'a> { 17 accounts: &'a mut Accounts, 18 manager: RelayPoolManager<'a>, 19 id_string_map: &'a mut HashMap<Id, String>, 20 } 21 22 impl View for RelayView<'_> { 23 fn ui(&mut self, ui: &mut egui::Ui) { 24 ui.add_space(24.0); 25 26 ui.horizontal(|ui| { 27 ui.with_layout(Layout::left_to_right(Align::Center), |ui| { 28 ui.label( 29 RichText::new("Relays").text_style(NotedeckTextStyle::Heading2.text_style()), 30 ); 31 }); 32 }); 33 34 ui.add_space(8.0); 35 36 egui::ScrollArea::vertical() 37 .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) 38 .auto_shrink([false; 2]) 39 .show(ui, |ui| { 40 if let Some(relay_to_remove) = self.show_relays(ui) { 41 self.accounts 42 .remove_advertised_relay(&relay_to_remove, self.manager.pool); 43 } 44 ui.add_space(8.0); 45 if let Some(relay_to_add) = self.show_add_relay_ui(ui) { 46 self.accounts 47 .add_advertised_relay(&relay_to_add, self.manager.pool); 48 } 49 }); 50 } 51 } 52 53 impl<'a> RelayView<'a> { 54 pub fn new( 55 accounts: &'a mut Accounts, 56 manager: RelayPoolManager<'a>, 57 id_string_map: &'a mut HashMap<Id, String>, 58 ) -> Self { 59 RelayView { 60 accounts, 61 manager, 62 id_string_map, 63 } 64 } 65 66 pub fn panel(&mut self, ui: &mut egui::Ui) { 67 egui::CentralPanel::default().show(ui.ctx(), |ui| self.ui(ui)); 68 } 69 70 /// Show the current relays and return a relay the user selected to delete 71 fn show_relays(&'a self, ui: &mut Ui) -> Option<String> { 72 let mut relay_to_remove = None; 73 for (index, relay_info) in self.manager.get_relay_infos().iter().enumerate() { 74 ui.add_space(8.0); 75 ui.vertical_centered_justified(|ui| { 76 relay_frame(ui).show(ui, |ui| { 77 ui.horizontal(|ui| { 78 ui.with_layout(Layout::left_to_right(Align::Center), |ui| { 79 Frame::none() 80 // This frame is needed to add margin because the label will be added to the outer frame first and centered vertically before the connection status is added so the vertical centering isn't accurate. 81 // TODO: remove this hack and actually center the url & status at the same time 82 .inner_margin(Margin::symmetric(0.0, 4.0)) 83 .show(ui, |ui| { 84 egui::ScrollArea::horizontal() 85 .id_salt(index) 86 .max_width( 87 ui.max_rect().width() 88 - get_right_side_width(relay_info.status), 89 ) // TODO: refactor to dynamically check the size of the 'right to left' portion and set the max width to be the screen width minus padding minus 'right to left' width 90 .show(ui, |ui| { 91 ui.label( 92 RichText::new(relay_info.relay_url) 93 .text_style( 94 NotedeckTextStyle::Monospace.text_style(), 95 ) 96 .color( 97 ui.style() 98 .visuals 99 .noninteractive() 100 .fg_stroke 101 .color, 102 ), 103 ); 104 }); 105 }); 106 }); 107 108 ui.with_layout(Layout::right_to_left(Align::Center), |ui| { 109 if ui.add(delete_button(ui.visuals().dark_mode)).clicked() { 110 relay_to_remove = Some(relay_info.relay_url.to_string()); 111 }; 112 113 show_connection_status(ui, relay_info.status); 114 }); 115 }); 116 }); 117 }); 118 } 119 relay_to_remove 120 } 121 122 const RELAY_PREFILL: &'static str = "wss://"; 123 124 fn show_add_relay_ui(&mut self, ui: &mut Ui) -> Option<String> { 125 let id = ui.id().with("add-relay)"); 126 match self.id_string_map.get(&id) { 127 None => { 128 ui.with_layout(Layout::top_down(Align::Min), |ui| { 129 let relay_button = add_relay_button(); 130 if ui.add(relay_button).clicked() { 131 debug!("add relay clicked"); 132 self.id_string_map 133 .insert(id, Self::RELAY_PREFILL.to_string()); 134 }; 135 }); 136 None 137 } 138 Some(_) => { 139 ui.with_layout(Layout::top_down(Align::Min), |ui| { 140 self.add_relay_entry(ui, id) 141 }) 142 .inner 143 } 144 } 145 } 146 147 pub fn add_relay_entry(&mut self, ui: &mut Ui, id: Id) -> Option<String> { 148 padding(16.0, ui, |ui| { 149 let text_buffer = self 150 .id_string_map 151 .entry(id) 152 .or_insert_with(|| Self::RELAY_PREFILL.to_string()); 153 let is_enabled = self.manager.is_valid_relay(text_buffer); 154 let text_edit = egui::TextEdit::singleline(text_buffer) 155 .hint_text( 156 RichText::new("Enter the relay here") 157 .text_style(NotedeckTextStyle::Body.text_style()), 158 ) 159 .vertical_align(Align::Center) 160 .desired_width(f32::INFINITY) 161 .min_size(Vec2::new(0.0, 40.0)) 162 .margin(Margin::same(12.0)); 163 ui.add(text_edit); 164 ui.add_space(8.0); 165 if ui 166 .add_sized(egui::vec2(50.0, 40.0), add_relay_button2(is_enabled)) 167 .clicked() 168 { 169 self.id_string_map.remove(&id) // remove and return the value 170 } else { 171 None 172 } 173 }) 174 .inner 175 } 176 } 177 178 fn add_relay_button() -> Button<'static> { 179 let img_data = egui::include_image!("../../../../assets/icons/add_relay_icon_4x.png"); 180 let img = Image::new(img_data).fit_to_exact_size(Vec2::new(48.0, 48.0)); 181 Button::image_and_text( 182 img, 183 RichText::new(" Add relay") 184 .size(16.0) 185 // TODO: this color should not be hard coded. Find some way to add it to the visuals 186 .color(PINK), 187 ) 188 .frame(false) 189 } 190 191 fn add_relay_button2(is_enabled: bool) -> impl egui::Widget + 'static { 192 move |ui: &mut egui::Ui| -> egui::Response { 193 let button_widget = sized_button("Add"); 194 ui.add_enabled(is_enabled, button_widget) 195 } 196 } 197 198 fn get_right_side_width(status: RelayStatus) -> f32 { 199 match status { 200 RelayStatus::Connected => 150.0, 201 RelayStatus::Connecting => 160.0, 202 RelayStatus::Disconnected => 175.0, 203 } 204 } 205 206 fn delete_button(_dark_mode: bool) -> egui::Button<'static> { 207 /* 208 let img_data = if dark_mode { 209 egui::include_image!("../../assets/icons/delete_icon_4x.png") 210 } else { 211 // TODO: use light delete icon 212 egui::include_image!("../../assets/icons/delete_icon_4x.png") 213 }; 214 */ 215 let img_data = egui::include_image!("../../../../assets/icons/delete_icon_4x.png"); 216 217 egui::Button::image(egui::Image::new(img_data).max_width(10.0)).frame(false) 218 } 219 220 fn relay_frame(ui: &mut Ui) -> Frame { 221 Frame::none() 222 .inner_margin(Margin::same(8.0)) 223 .rounding(ui.style().noninteractive().rounding) 224 .stroke(ui.style().visuals.noninteractive().bg_stroke) 225 } 226 227 fn show_connection_status(ui: &mut Ui, status: RelayStatus) { 228 let fg_color = match status { 229 RelayStatus::Connected => ui.visuals().selection.bg_fill, 230 RelayStatus::Connecting => ui.visuals().warn_fg_color, 231 RelayStatus::Disconnected => ui.visuals().error_fg_color, 232 }; 233 let bg_color = egui::lerp(Rgba::from(fg_color)..=Rgba::BLACK, 0.8).into(); 234 235 let label_text = match status { 236 RelayStatus::Connected => "Connected", 237 RelayStatus::Connecting => "Connecting...", 238 RelayStatus::Disconnected => "Not Connected", 239 }; 240 241 let frame = Frame::none() 242 .rounding(Rounding::same(100.0)) 243 .fill(bg_color) 244 .inner_margin(Margin::symmetric(12.0, 4.0)); 245 246 frame.show(ui, |ui| { 247 ui.label(RichText::new(label_text).color(fg_color)); 248 ui.add(get_connection_icon(status)); 249 }); 250 } 251 252 fn get_connection_icon(status: RelayStatus) -> egui::Image<'static> { 253 let img_data = match status { 254 RelayStatus::Connected => { 255 egui::include_image!("../../../../assets/icons/connected_icon_4x.png") 256 } 257 RelayStatus::Connecting => { 258 egui::include_image!("../../../../assets/icons/connecting_icon_4x.png") 259 } 260 RelayStatus::Disconnected => { 261 egui::include_image!("../../../../assets/icons/disconnected_icon_4x.png") 262 } 263 }; 264 265 egui::Image::new(img_data) 266 } 267 268 // PREVIEWS 269 270 mod preview { 271 use super::*; 272 use crate::test_data::sample_pool; 273 use notedeck::{App, AppContext}; 274 275 pub struct RelayViewPreview { 276 pool: RelayPool, 277 } 278 279 impl RelayViewPreview { 280 fn new() -> Self { 281 RelayViewPreview { 282 pool: sample_pool(), 283 } 284 } 285 } 286 287 impl App for RelayViewPreview { 288 fn update(&mut self, app: &mut AppContext<'_>, ui: &mut egui::Ui) { 289 self.pool.try_recv(); 290 let mut id_string_map = HashMap::new(); 291 RelayView::new( 292 app.accounts, 293 RelayPoolManager::new(&mut self.pool), 294 &mut id_string_map, 295 ) 296 .ui(ui); 297 } 298 } 299 300 impl Preview for RelayView<'_> { 301 type Prev = RelayViewPreview; 302 303 fn preview(_cfg: PreviewConfig) -> Self::Prev { 304 RelayViewPreview::new() 305 } 306 } 307 }