notedeck

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

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 }